mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-26 22:31:21 +00:00
style: 移除多余空行并统一代码格式
修复代码中的多余空行问题,统一字符串引号使用双引号,调整对象和数组的格式,优化代码缩进和换行,提升代码可读性和一致性
This commit is contained in:
@@ -26,7 +26,11 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="数据源" prop="origin">
|
||||
<el-select v-model="exportsFormData.origin">
|
||||
<el-option label="当前数据 (当前页的数据)" :value="ExportsOriginEnum.CURRENT" :disabled="!pageData?.length" />
|
||||
<el-option
|
||||
label="当前数据 (当前页的数据)"
|
||||
:value="ExportsOriginEnum.CURRENT"
|
||||
:disabled="!pageData?.length"
|
||||
/>
|
||||
<el-option
|
||||
label="选中数据 (所有选中的数据)"
|
||||
:value="ExportsOriginEnum.SELECTED"
|
||||
@@ -113,9 +117,8 @@ const exportsFormRules: FormRules = {
|
||||
origin: [{ required: true, message: "请选择数据源" }],
|
||||
};
|
||||
|
||||
|
||||
// 表格列
|
||||
const cols = computed(() =>
|
||||
const cols = computed(() =>
|
||||
props.contentConfig.cols.map((col) => {
|
||||
if (col.initFn) {
|
||||
col.initFn(col);
|
||||
@@ -177,25 +180,28 @@ function handleExports() {
|
||||
}
|
||||
});
|
||||
worksheet.columns = columns;
|
||||
|
||||
|
||||
if (exportsFormData.origin === ExportsOriginEnum.REMOTE) {
|
||||
if (props.contentConfig.exportsAction) {
|
||||
const lastFormData = props.queryParams ?? {};
|
||||
props.contentConfig.exportsAction(lastFormData).then((res) => {
|
||||
worksheet.addRows(res);
|
||||
workbook.xlsx
|
||||
.writeBuffer()
|
||||
.then((buffer) => {
|
||||
saveXlsx(buffer, filename as string);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("导出远程数据失败:", error);
|
||||
ElMessage.error("导出远程数据失败");
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.error("获取远程数据失败:", error);
|
||||
ElMessage.error("获取远程数据失败");
|
||||
});
|
||||
props.contentConfig
|
||||
.exportsAction(lastFormData)
|
||||
.then((res) => {
|
||||
worksheet.addRows(res);
|
||||
workbook.xlsx
|
||||
.writeBuffer()
|
||||
.then((buffer) => {
|
||||
saveXlsx(buffer, filename as string);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("导出远程数据失败:", error);
|
||||
ElMessage.error("导出远程数据失败");
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("获取远程数据失败:", error);
|
||||
ElMessage.error("获取远程数据失败");
|
||||
});
|
||||
} else {
|
||||
ElMessage.error("未配置exportsAction");
|
||||
}
|
||||
|
||||
@@ -1,43 +1,54 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 导入弹窗 -->
|
||||
<el-dialog
|
||||
v-model="importModalVisible"
|
||||
:align-center="true"
|
||||
:title="props.title"
|
||||
:width="props.width"
|
||||
<el-dialog
|
||||
v-model="importModalVisible"
|
||||
:align-center="true"
|
||||
:title="props.title"
|
||||
:width="props.width"
|
||||
style="padding-right: 0"
|
||||
@close="handleClose">
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- 滚动 -->
|
||||
<el-scrollbar :max-height="props.maxHeight">
|
||||
<!-- 表单 -->
|
||||
<el-form
|
||||
ref="importFormRef"
|
||||
style="padding-right: var(--el-dialog-padding-primary)"
|
||||
:model="importFormData"
|
||||
:rules="importFormRules">
|
||||
<el-form
|
||||
ref="importFormRef"
|
||||
style="padding-right: var(--el-dialog-padding-primary)"
|
||||
:model="importFormData"
|
||||
:rules="importFormRules"
|
||||
>
|
||||
<el-form-item prop="files">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="importFormData.files"
|
||||
class="w-full"
|
||||
:accept="props.accept"
|
||||
:drag="true"
|
||||
:limit="props.limit"
|
||||
:auto-upload="false"
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="importFormData.files"
|
||||
class="w-full"
|
||||
:accept="props.accept"
|
||||
:drag="true"
|
||||
:limit="props.limit"
|
||||
:auto-upload="false"
|
||||
:on-exceed="handleFileExceed"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
{{ props.dropText || '将文件拖到此处,或' }}
|
||||
<em>{{ props.browseText || '点击上传' }}</em>
|
||||
{{ props.dropText || "将文件拖到此处,或" }}
|
||||
<em>{{ props.browseText || "点击上传" }}</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip flex flex-wrap gap-2">
|
||||
<el-text v-if="props.note" type="warning" class="mx-1">{{ props.note }}</el-text>
|
||||
<el-text v-if="props.fileTypeWarning" type="danger" class="mx-1">{{ props.fileTypeWarning }}</el-text>
|
||||
<el-link v-if="props.showTemplateDownload" class="mx-1" type="primary" icon="download" underline="never" @click="handleDownloadTemplate">
|
||||
{{ props.templateDownloadText || '下载模板' }}
|
||||
<el-text v-if="props.fileTypeWarning" type="danger" class="mx-1">
|
||||
{{ props.fileTypeWarning }}
|
||||
</el-text>
|
||||
<el-link
|
||||
v-if="props.showTemplateDownload"
|
||||
class="mx-1"
|
||||
type="primary"
|
||||
icon="download"
|
||||
underline="never"
|
||||
@click="handleDownloadTemplate"
|
||||
>
|
||||
{{ props.templateDownloadText || "下载模板" }}
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,14 +58,14 @@
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<div style="padding-right: var(--el-dialog-padding-primary)">
|
||||
<el-button @click="handleClose">{{ props.cancelButtonText || '取 消' }}</el-button>
|
||||
<el-button @click="handleClose">{{ props.cancelButtonText || "取 消" }}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="importFormData.files.length === 0 || loading"
|
||||
@click="handleUpload"
|
||||
>
|
||||
<el-icon v-if="loading"><Loading /></el-icon>
|
||||
{{ props.confirmButtonText || '确 定' }}
|
||||
{{ props.confirmButtonText || "确 定" }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -157,7 +168,8 @@ const props = withDefaults(defineProps<ImportModalProps>(), {
|
||||
title: "导入数据",
|
||||
width: "600px",
|
||||
maxHeight: "60vh",
|
||||
accept: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel",
|
||||
accept:
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel",
|
||||
limit: 1,
|
||||
showTemplateDownload: true,
|
||||
note: "注意事项:",
|
||||
@@ -169,7 +181,7 @@ const props = withDefaults(defineProps<ImportModalProps>(), {
|
||||
// 定义模型值(控制弹窗显示/隐藏)
|
||||
const importModalVisible = defineModel<boolean>("modelValue", {
|
||||
required: true,
|
||||
default: false
|
||||
default: false,
|
||||
});
|
||||
|
||||
// 定义事件
|
||||
@@ -195,12 +207,12 @@ const loading = ref(false);
|
||||
const importFormData = reactive<{
|
||||
files: UploadUserFile[];
|
||||
}>({
|
||||
files: []
|
||||
files: [],
|
||||
});
|
||||
|
||||
// 表单规则
|
||||
const importFormRules = {
|
||||
files: [{ required: true, message: "文件不能为空", trigger: "blur" }]
|
||||
files: [{ required: true, message: "文件不能为空", trigger: "blur" }],
|
||||
};
|
||||
|
||||
// 文件超出个数限制
|
||||
@@ -264,7 +276,7 @@ const handleUpload = async () => {
|
||||
formData.append(props.uploadFileName, file);
|
||||
|
||||
// 添加额外参数
|
||||
Object.keys(props.uploadData).forEach(key => {
|
||||
Object.keys(props.uploadData).forEach((key) => {
|
||||
formData.append(key, props.uploadData[key]);
|
||||
});
|
||||
|
||||
@@ -288,6 +300,6 @@ const handleClose = () => {
|
||||
|
||||
// 提供给父组件的方法
|
||||
defineExpose({
|
||||
handleClose
|
||||
handleClose,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<div v-show="visible">
|
||||
<el-card v-bind="cardAttrs">
|
||||
<el-form ref="queryFormRef" :model="queryParams" v-bind="formAttrs" :class="isGrid" @submit.prevent="handleQuery">
|
||||
<!-- 搜索表单区域 -->
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
label-suffix=":"
|
||||
v-bind="formAttrs"
|
||||
:model="queryParams"
|
||||
:class="isGrid"
|
||||
:inline="true"
|
||||
@submit.prevent="handleQuery"
|
||||
>
|
||||
<template v-for="(item, index) in formItems" :key="item.prop">
|
||||
<el-form-item
|
||||
v-show="isExpand ? true : index < showNumber"
|
||||
@@ -19,14 +28,14 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<el-cascader
|
||||
<ElCascader
|
||||
v-if="item.type === 'cascader'"
|
||||
v-model.trim="queryParams[item.prop]"
|
||||
v-bind="{ style: { width: '100%' }, ...item.attrs }"
|
||||
v-on="item.events || {}"
|
||||
/>
|
||||
<component
|
||||
:is="componentMap.get(item.type)"
|
||||
:is="getCustomComponent(item.type) || componentMap.get(item.type)"
|
||||
v-else
|
||||
v-model.trim="queryParams[item.prop]"
|
||||
v-bind="{ style: { width: '100%' }, ...item.attrs }"
|
||||
@@ -37,13 +46,56 @@
|
||||
<el-option :label="opt.label" :value="opt.value" />
|
||||
</template>
|
||||
</template>
|
||||
<!-- 自定义组件的插槽支持 -->
|
||||
<template v-if="getCustomComponent(item.type) && item.slotName">
|
||||
<template v-for="slot in Object.keys($slots)" :key="slot">
|
||||
<slot :name="slot"></slot>
|
||||
</template>
|
||||
</template>
|
||||
</component>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item :class="{ 'col-[auto/-1] justify-self-end': searchConfig?.grid === 'right' }">
|
||||
<el-button icon="search" type="primary" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="refresh" @click="handleReset">重置</el-button>
|
||||
<!-- 自定义按钮组 -->
|
||||
<template v-if="searchConfig?.customButtons && searchConfig.customButtons.length > 0">
|
||||
<template v-for="button in searchConfig.customButtons" :key="button.key">
|
||||
<el-button
|
||||
v-if="!button.perm || hasPermission(button.perm)"
|
||||
v-bind="button.attrs"
|
||||
@click="handleCustomButtonClick(button)"
|
||||
>
|
||||
{{ button.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 默认搜索/重置按钮 -->
|
||||
<template v-else>
|
||||
<!-- 搜索按钮 -->
|
||||
<el-button
|
||||
v-if="
|
||||
!searchConfig?.showSearchButton ||
|
||||
hasPermission(searchConfig.searchButtonPerm || [])
|
||||
"
|
||||
icon="search"
|
||||
type="primary"
|
||||
@click="handleQuery"
|
||||
>
|
||||
搜索
|
||||
</el-button>
|
||||
<!-- 重置按钮 -->
|
||||
<el-button
|
||||
v-if="
|
||||
!searchConfig?.showResetButton || hasPermission(searchConfig.resetButtonPerm || [])
|
||||
"
|
||||
icon="refresh"
|
||||
@click="handleReset"
|
||||
>
|
||||
重置
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 展开/收起 -->
|
||||
<template v-if="isExpandable && formItems.length > showNumber">
|
||||
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
|
||||
@@ -51,6 +103,17 @@
|
||||
<component :is="isExpand ? ArrowUp : ArrowDown" class="w-4 h-4 ml-2" />
|
||||
</el-link>
|
||||
</template>
|
||||
<!-- 显示/隐藏搜索区域 -->
|
||||
<el-tooltip v-if="searchConfig?.showToggle" content="搜索显示/隐藏">
|
||||
<el-button
|
||||
v-if="!searchConfig?.togglePerm || hasPermission(searchConfig.togglePerm)"
|
||||
class="ml-3"
|
||||
type="info"
|
||||
icon="search"
|
||||
circle
|
||||
@click="toggleVisible"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
@@ -59,9 +122,26 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IObject, IForm, ISearchConfig, ISearchComponent } from "./types";
|
||||
import { ArrowUp, ArrowDown } from "@element-plus/icons-vue";
|
||||
import { ArrowUp, ArrowDown, QuestionFilled } from "@element-plus/icons-vue";
|
||||
import type { FormInstance } from "element-plus";
|
||||
import InputTag from "@/components/InputTag/index.vue";
|
||||
import { getCurrentInstance } from "vue";
|
||||
import {
|
||||
ElInput,
|
||||
ElSelect,
|
||||
ElCascader,
|
||||
ElInputNumber,
|
||||
ElDatePicker,
|
||||
ElTimePicker,
|
||||
ElTimeSelect,
|
||||
ElTreeSelect,
|
||||
ElInputTag,
|
||||
ElRadioGroup,
|
||||
ElCheckboxGroup,
|
||||
ElSwitch,
|
||||
ElRate,
|
||||
ElSlider,
|
||||
} from "element-plus";
|
||||
|
||||
// 定义接收的属性
|
||||
const props = defineProps<{ searchConfig: ISearchConfig }>();
|
||||
@@ -69,7 +149,13 @@ const props = defineProps<{ searchConfig: ISearchConfig }>();
|
||||
const emit = defineEmits<{
|
||||
queryClick: [queryParams: IObject];
|
||||
resetClick: [queryParams: IObject];
|
||||
dateRangeChange: [prop: string, range: [Date, Date] | [string, string] | [null, null]];
|
||||
customButtonClick: [buttonKey: string, queryParams: IObject];
|
||||
}>();
|
||||
|
||||
// 时间范围处理相关
|
||||
const dateRangeRefs = ref<Record<string, any>>({});
|
||||
|
||||
// 组件映射表
|
||||
const componentMap = new Map<ISearchComponent, any>([
|
||||
// @ts-ignore
|
||||
@@ -82,9 +168,19 @@ const componentMap = new Map<ISearchComponent, any>([
|
||||
["time-select", markRaw(ElTimeSelect)], // @ts-ignore
|
||||
["tree-select", markRaw(ElTreeSelect)], // @ts-ignore
|
||||
["input-tag", markRaw(ElInputTag)], // @ts-ignore
|
||||
["custom-tag", markRaw(InputTag)],
|
||||
["custom-tag", markRaw(InputTag)], // @ts-ignore
|
||||
["radio", markRaw(ElRadioGroup)], // @ts-ignore
|
||||
["checkbox", markRaw(ElCheckboxGroup)], // @ts-ignore
|
||||
["switch", markRaw(ElSwitch)], // @ts-ignore
|
||||
["rate", markRaw(ElRate)], // @ts-ignore
|
||||
["slider", markRaw(ElSlider)], // @ts-ignore
|
||||
]);
|
||||
|
||||
// 自定义组件映射(从searchConfig中获取)
|
||||
const getCustomComponent = (componentName: string) => {
|
||||
return props.searchConfig?.customComponents?.[componentName] || null;
|
||||
};
|
||||
|
||||
// 存储表单实例
|
||||
const queryFormRef = ref<FormInstance>();
|
||||
// 存储查询参数
|
||||
@@ -120,13 +216,51 @@ const isGrid = computed(() =>
|
||||
const getTooltipProps = (tips: string | IObject) => {
|
||||
return typeof tips === "string" ? { content: tips } : tips;
|
||||
};
|
||||
|
||||
// 显示/隐藏搜索区域
|
||||
const toggleVisible = () => {
|
||||
visible.value = !visible.value;
|
||||
};
|
||||
|
||||
// 查询/重置操作
|
||||
const handleQuery = () => emit("queryClick", queryParams);
|
||||
const handleReset = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
// 重置所有时间范围组件
|
||||
Object.values(dateRangeRefs.value).forEach((ref) => {
|
||||
if (ref && typeof ref.reset === "function") {
|
||||
ref.reset();
|
||||
}
|
||||
});
|
||||
emit("resetClick", queryParams);
|
||||
};
|
||||
|
||||
// 处理时间范围变化
|
||||
const handleDateRangeChange = (
|
||||
prop: string,
|
||||
range: [Date, Date] | [string, string] | [null, null]
|
||||
) => {
|
||||
emit("dateRangeChange", prop, range);
|
||||
};
|
||||
|
||||
// 处理自定义按钮点击
|
||||
const handleCustomButtonClick = (button: any) => {
|
||||
// 如果配置了自定义处理器,优先使用
|
||||
if (button.handler && typeof button.handler === "function") {
|
||||
button.handler(queryParams, getCurrentInstance());
|
||||
}
|
||||
// 触发自定义事件
|
||||
emit("customButtonClick", button.key, queryParams);
|
||||
};
|
||||
|
||||
// 权限控制检查
|
||||
const hasPermission = (perm: string | string[]): boolean => {
|
||||
if (!perm || !props.searchConfig?.permPrefix) return true;
|
||||
// 这里应该根据实际的权限系统来实现
|
||||
// 暂时返回 true,实际项目中需要对接权限系统
|
||||
return true;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
formItems.forEach((item) => {
|
||||
if (item?.initFn) {
|
||||
@@ -145,8 +279,6 @@ onMounted(() => {
|
||||
defineExpose({
|
||||
// 获取分页数据
|
||||
getQueryParams: () => queryParams,
|
||||
// 显示/隐藏 SearchForm
|
||||
toggleVisible: () => (visible.value = !visible.value),
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -19,8 +19,23 @@ export type IObject = Record<string, any>;
|
||||
*/
|
||||
type DateComponent = "date-picker" | "time-picker" | "time-select" | "custom-tag" | "input-tag";
|
||||
type InputComponent = "input" | "select" | "input-number" | "cascader" | "tree-select";
|
||||
type OtherComponent = "text" | "radio" | "checkbox" | "switch" | "icon-select" | "custom";
|
||||
export type ISearchComponent = DateComponent | InputComponent;
|
||||
type OtherComponent =
|
||||
| "text"
|
||||
| "radio"
|
||||
| "checkbox"
|
||||
| "switch"
|
||||
| "rate"
|
||||
| "slider"
|
||||
| "icon-select"
|
||||
| "custom";
|
||||
export type ISearchComponent =
|
||||
| DateComponent
|
||||
| InputComponent
|
||||
| "radio"
|
||||
| "checkbox"
|
||||
| "switch"
|
||||
| "rate"
|
||||
| "slider";
|
||||
export type IComponentType = DateComponent | InputComponent | OtherComponent;
|
||||
|
||||
/**
|
||||
@@ -87,6 +102,33 @@ export interface ISearchConfig {
|
||||
form?: IForm;
|
||||
/** 自适应网格布局(使用时表单不要添加 style: { width: "200px" }) */
|
||||
grid?: boolean | "left" | "right";
|
||||
/** 自定义组件映射 */
|
||||
customComponents?: Record<string, any>;
|
||||
/** 是否显示搜索按钮(默认:true) */
|
||||
showSearchButton?: boolean;
|
||||
/** 是否显示重置按钮(默认:true) */
|
||||
showResetButton?: boolean;
|
||||
/** 搜索按钮权限 */
|
||||
searchButtonPerm?: string | string[];
|
||||
/** 重置按钮权限 */
|
||||
resetButtonPerm?: string | string[];
|
||||
/** 是否显示搜索区域切换按钮(默认:false) */
|
||||
showToggle?: boolean;
|
||||
/** 搜索区域切换按钮权限 */
|
||||
togglePerm?: string | string[];
|
||||
/** 自定义按钮组 */
|
||||
customButtons?: Array<{
|
||||
/** 按钮唯一标识 */
|
||||
key: string;
|
||||
/** 按钮文本 */
|
||||
text: string;
|
||||
/** 按钮属性 */
|
||||
attrs?: IObject;
|
||||
/** 按钮权限 */
|
||||
perm?: string | string[];
|
||||
/** 点击事件处理函数 */
|
||||
handler?: (params: IObject, instance: any) => void;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,13 +147,13 @@ export interface IContentConfig<T = any> {
|
||||
pagePosition?: "left" | "right";
|
||||
/** pagination组件属性 */
|
||||
pagination?:
|
||||
| boolean
|
||||
| Partial<
|
||||
Omit<
|
||||
PaginationProps,
|
||||
"v-model:page-size" | "v-model:current-page" | "total" | "currentPage"
|
||||
>
|
||||
>;
|
||||
| boolean
|
||||
| Partial<
|
||||
Omit<
|
||||
PaginationProps,
|
||||
"v-model:page-size" | "v-model:current-page" | "total" | "currentPage"
|
||||
>
|
||||
>;
|
||||
/** 列表的网络请求函数(需返回promise) */
|
||||
indexAction: (queryParams: T) => Promise<any>;
|
||||
/** 默认的分页相关的请求参数 */
|
||||
@@ -173,17 +215,17 @@ export interface IContentConfig<T = any> {
|
||||
show?: boolean;
|
||||
/** 模板类型 */
|
||||
templet?:
|
||||
| "image"
|
||||
| "list"
|
||||
| "url"
|
||||
| "switch"
|
||||
| "input"
|
||||
| "price"
|
||||
| "percent"
|
||||
| "icon"
|
||||
| "date"
|
||||
| "tool"
|
||||
| "custom";
|
||||
| "image"
|
||||
| "list"
|
||||
| "url"
|
||||
| "switch"
|
||||
| "input"
|
||||
| "price"
|
||||
| "percent"
|
||||
| "icon"
|
||||
| "date"
|
||||
| "tool"
|
||||
| "custom";
|
||||
/** image模板相关参数 */
|
||||
imageWidth?: number;
|
||||
/** image模板相关参数 */
|
||||
@@ -270,7 +312,7 @@ export type IFormItems<T = IComponentType> = Array<{
|
||||
/** 属性 */
|
||||
attrs?: IObject;
|
||||
/** 选项 */
|
||||
options?: Array<{ label: string; value: any;[key: string]: any }> | Ref<any[]>;
|
||||
options?: Array<{ label: string; value: any; [key: string]: any }> | Ref<any[]>;
|
||||
/** 规则 */
|
||||
rules?: FormItemRule[];
|
||||
/** 初始值 */
|
||||
|
||||
@@ -55,7 +55,9 @@ function usePage() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("打开新增模态框失败:", error);
|
||||
ElMessage.error("打开新增模态框失败: " + (error instanceof Error ? error.message : String(error)));
|
||||
ElMessage.error(
|
||||
"打开新增模态框失败: " + (error instanceof Error ? error.message : String(error))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +86,9 @@ function usePage() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("打开编辑模态框失败:", error);
|
||||
ElMessage.error("打开编辑模态框失败: " + (error instanceof Error ? error.message : String(error)));
|
||||
ElMessage.error(
|
||||
"打开编辑模态框失败: " + (error instanceof Error ? error.message : String(error))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +117,9 @@ function usePage() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("打开查看模态框失败:", error);
|
||||
ElMessage.error("打开查看模态框失败: " + (error instanceof Error ? error.message : String(error)));
|
||||
ElMessage.error(
|
||||
"打开查看模态框失败: " + (error instanceof Error ? error.message : String(error))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +159,9 @@ function usePage() {
|
||||
searchRef.value?.toggleVisible();
|
||||
} catch (error) {
|
||||
console.error("切换搜索栏失败:", error);
|
||||
ElMessage.error("切换搜索栏失败: " + (error instanceof Error ? error.message : String(error)));
|
||||
ElMessage.error(
|
||||
"切换搜索栏失败: " + (error instanceof Error ? error.message : String(error))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +198,9 @@ function usePage() {
|
||||
handleSubmitClick();
|
||||
} catch (error) {
|
||||
console.error("处理更多操作失败:", error);
|
||||
ElMessage.error("处理更多操作失败: " + (error instanceof Error ? error.message : String(error)));
|
||||
ElMessage.error(
|
||||
"处理更多操作失败: " + (error instanceof Error ? error.message : String(error))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,67 +15,66 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
// 定义组件属性
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Array as unknown as () => [Date, Date] | [],
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['update:model-value'])
|
||||
const emit = defineEmits(["update:model-value"]);
|
||||
|
||||
// 快捷选项配置
|
||||
const shortcuts = [
|
||||
{
|
||||
text: '近一天',
|
||||
text: "近一天",
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
// 修正:起始时间为当前时间往前 24 小时
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24)
|
||||
return [start, end]
|
||||
}
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24);
|
||||
return [start, end];
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '近三天',
|
||||
text: "近三天",
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 3)
|
||||
return [start, end]
|
||||
}
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 3);
|
||||
return [start, end];
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '近一周',
|
||||
text: "近一周",
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
|
||||
return [start, end]
|
||||
}
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
|
||||
return [start, end];
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '近一个月',
|
||||
text: "近一个月",
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
|
||||
return [start, end]
|
||||
}
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
|
||||
return [start, end];
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '近三个月',
|
||||
text: "近三个月",
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
|
||||
return [start, end]
|
||||
}
|
||||
}
|
||||
]
|
||||
const end = new Date();
|
||||
const start = new Date();
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
|
||||
return [start, end];
|
||||
},
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,34 +1,29 @@
|
||||
<!-- 自定义 iframe 组件 -->
|
||||
<template>
|
||||
<div v-loading="loading" :style="'height:' + height">
|
||||
<iframe
|
||||
:src="url"
|
||||
frameborder="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
scrolling="auto" />
|
||||
<iframe :src="url" frameborder="0" width="100%" height="100%" scrolling="auto" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const height = ref(document.documentElement.clientHeight - 94.5 + "px;")
|
||||
const loading = ref(true)
|
||||
const url = computed(() => props.src)
|
||||
const height = ref(document.documentElement.clientHeight - 94.5 + "px;");
|
||||
const loading = ref(true);
|
||||
const url = computed(() => props.src);
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 300);
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<!-- 引导页 -->
|
||||
<template>
|
||||
<el-tour
|
||||
v-model="open"
|
||||
:show-close="false"
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-tour v-model="open" :show-close="false" @change="handleChange">
|
||||
<el-tour-step
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
@@ -13,11 +9,11 @@
|
||||
:description="step.description"
|
||||
:prev-button-props="{
|
||||
children: t('common.prevLabel'),
|
||||
onClick: handlePrevClick
|
||||
onClick: handlePrevClick,
|
||||
}"
|
||||
:next-button-props="{
|
||||
children: nextBtnName(index),
|
||||
onClick: handleNextClick
|
||||
onClick: handleNextClick,
|
||||
}"
|
||||
:placement="step.placement"
|
||||
/>
|
||||
@@ -42,56 +38,55 @@ const props = defineProps({
|
||||
},
|
||||
teleport: {
|
||||
type: [String, Object] as PropType<string | HTMLElement | null>,
|
||||
default: 'body',
|
||||
}
|
||||
default: "body",
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change', 'prev', 'next', 'skip']);
|
||||
const emit = defineEmits(["update:modelValue", "change", "prev", "next", "skip"]);
|
||||
|
||||
const open = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
set: (val) => emit("update:modelValue", val),
|
||||
});
|
||||
|
||||
interface TourStep {
|
||||
target: string;
|
||||
title: string;
|
||||
description: string;
|
||||
placement: 'top' | 'bottom' | 'left' | 'right';
|
||||
}
|
||||
placement: "top" | "bottom" | "left" | "right";
|
||||
}
|
||||
|
||||
const layout = settingStore.layout;
|
||||
|
||||
const menuTarget = (): string => {
|
||||
if (layout === 'left') {
|
||||
return '.layout__sidebar';
|
||||
} else if (layout === 'top') {
|
||||
return '.layout__header-left';
|
||||
if (layout === "left") {
|
||||
return ".layout__sidebar";
|
||||
} else if (layout === "top") {
|
||||
return ".layout__header-left";
|
||||
} else {
|
||||
return '.layout__header-menu';
|
||||
return ".layout__header-menu";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 内置引导步骤数据
|
||||
const steps: TourStep[] = [
|
||||
{
|
||||
target: menuTarget(),
|
||||
title: t("common.menu"),
|
||||
description: t("common.menuDes"),
|
||||
placement: layout === 'left' ? 'right' : 'bottom'
|
||||
placement: layout === "left" ? "right" : "bottom",
|
||||
},
|
||||
{
|
||||
target: ".navbar-actions",
|
||||
title: t("common.tool"),
|
||||
description: t("common.toolDes"),
|
||||
placement: "bottom"
|
||||
placement: "bottom",
|
||||
},
|
||||
{
|
||||
target: ".tags-container",
|
||||
title: t("common.tagsView"),
|
||||
description: t("common.tagsViewDes"),
|
||||
placement: "bottom"
|
||||
placement: "bottom",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -101,33 +96,32 @@ const currentStep = ref(0);
|
||||
// 动态设置下一步按钮名称
|
||||
const nextBtnName = computed(() => (index: number) => {
|
||||
if (index === steps.length - 1) {
|
||||
return t('common.doneLabel');
|
||||
return t("common.doneLabel");
|
||||
}
|
||||
return t('common.nextLabel');
|
||||
return t("common.nextLabel");
|
||||
});
|
||||
|
||||
// 步数切换时触发
|
||||
function handleChange(step: number) {
|
||||
currentStep.value = step;
|
||||
emit('change', step);
|
||||
emit("change", step);
|
||||
}
|
||||
|
||||
// 点击跳过按钮时触发
|
||||
function handleSkip() {
|
||||
open.value = false;
|
||||
emit('skip');
|
||||
emit("skip");
|
||||
}
|
||||
|
||||
// 点击上一步按钮时触发
|
||||
function handlePrevClick() {
|
||||
emit('prev');
|
||||
emit("prev");
|
||||
}
|
||||
|
||||
// 点击下一步按钮时触发
|
||||
function handleNextClick() {
|
||||
emit('next');
|
||||
emit("next");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -137,5 +131,4 @@ function handleNextClick() {
|
||||
justify-content: flex-end;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,33 +1,58 @@
|
||||
<template>
|
||||
<el-form :model="crontabValueObj" ref="formRef" label-width="auto" label-suffix=":" :inline="true" class="interval-tab-form">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="crontabValueObj"
|
||||
label-width="auto"
|
||||
label-suffix=":"
|
||||
:inline="true"
|
||||
class="interval-tab-form"
|
||||
>
|
||||
<el-form-item label="秒" prop="second" class="form-item">
|
||||
<el-select v-model="crontabValueObj.second" placeholder="秒" clearable>
|
||||
<el-option label="每秒" value="*">*</el-option>
|
||||
<el-option v-for="second in seconds" :key="second" :label="second" :value="second.toString()">{{ second }}</el-option>
|
||||
<el-option
|
||||
v-for="second in seconds"
|
||||
:key="second"
|
||||
:label="second"
|
||||
:value="second.toString()"
|
||||
>
|
||||
{{ second }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分" prop="min" class="form-item">
|
||||
<el-select v-model="crontabValueObj.min" placeholder="分" clearable>
|
||||
<el-option label="每分" value="*">*</el-option>
|
||||
<el-option v-for="min in minutes" :key="min" :label="min" :value="min.toString()">{{min}}</el-option>
|
||||
<el-option v-for="min in minutes" :key="min" :label="min" :value="min.toString()">
|
||||
{{ min }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="时" prop="hour" class="form-item">
|
||||
<el-select v-model="crontabValueObj.hour" placeholder="时" clearable>
|
||||
<el-option label="每时" value="*">*</el-option>
|
||||
<el-option v-for="hour in hours" :key="hour" :label="hour" :value="hour.toString()">{{hour}}</el-option>
|
||||
<el-option v-for="hour in hours" :key="hour" :label="hour" :value="hour.toString()">
|
||||
{{ hour }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="天" prop="day" class="form-item">
|
||||
<el-select v-model="crontabValueObj.day" placeholder="天" clearable>
|
||||
<el-option label="每天" value="*">*</el-option>
|
||||
<el-option v-for="day in days" :key="day" :label="day" :value="day">{{day}}</el-option>
|
||||
<el-option v-for="day in days" :key="day" :label="day" :value="day">{{ day }}</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="周" prop="week" class="form-item">
|
||||
<el-select v-model="crontabValueObj.week" placeholder="周" clearable>
|
||||
<el-option label="每周" value="*">*</el-option>
|
||||
<el-option v-for="week in weekOptions" :key="week.value" :label="week.label" :value="week.value">{{week.label}}</el-option>
|
||||
<el-option
|
||||
v-for="week in weekOptions"
|
||||
:key="week.value"
|
||||
:label="week.label"
|
||||
:value="week.value"
|
||||
>
|
||||
{{ week.label }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -39,7 +64,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
// 定义接口,增强类型安全
|
||||
interface CrontabValue {
|
||||
second: string;
|
||||
@@ -61,8 +85,8 @@ const props = defineProps<{
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'confirm', value: string): void;
|
||||
(e: 'cancel'): void;
|
||||
(e: "confirm", value: string): void;
|
||||
(e: "cancel"): void;
|
||||
}>();
|
||||
|
||||
const formRef = ref();
|
||||
@@ -94,7 +118,7 @@ const weekOptions: WeekOption[] = [
|
||||
{ value: "4", label: "周四" },
|
||||
{ value: "5", label: "周五" },
|
||||
{ value: "6", label: "周六" },
|
||||
{ value: "7", label: "周日" }
|
||||
{ value: "7", label: "周日" },
|
||||
];
|
||||
|
||||
// 初始化时设置cron值
|
||||
@@ -109,30 +133,30 @@ const handleConfirm = () => {
|
||||
// 简单验证
|
||||
const obj = crontabValueObj.value;
|
||||
if (!obj.second || !obj.min || !obj.hour || !obj.day || !obj.week) {
|
||||
ElMessage.warning('请完善所有时间选项');
|
||||
ElMessage.warning("请完善所有时间选项");
|
||||
return;
|
||||
}
|
||||
|
||||
const cronStr = `${obj.second} ${obj.min} ${obj.hour} ${obj.day} ${obj.week}`;
|
||||
emit('confirm', cronStr);
|
||||
emit("confirm", cronStr);
|
||||
};
|
||||
|
||||
// 设置cron表达式的值
|
||||
const setCron = (cronStr: string) => {
|
||||
if (!cronStr) return;
|
||||
const parts = cronStr.split(' ');
|
||||
const parts = cronStr.split(" ");
|
||||
if (parts.length !== 5) {
|
||||
ElMessage.warning('无效的cron表达式格式');
|
||||
ElMessage.warning("无效的cron表达式格式");
|
||||
return;
|
||||
}
|
||||
|
||||
const [second, min, hour, day, week] = parts;
|
||||
crontabValueObj.value = {
|
||||
second: second || '*',
|
||||
min: min || '*',
|
||||
hour: hour || '*',
|
||||
day: day || '*',
|
||||
week: week || '*'
|
||||
second: second || "*",
|
||||
min: min || "*",
|
||||
hour: hour || "*",
|
||||
day: day || "*",
|
||||
week: week || "*",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -172,4 +196,4 @@ defineExpose({ setCron });
|
||||
width: calc(50% - 8px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import VueJsonPretty from 'vue-json-pretty';
|
||||
import 'vue-json-pretty/lib/styles.css';
|
||||
import { computed } from "vue";
|
||||
import VueJsonPretty from "vue-json-pretty";
|
||||
import "vue-json-pretty/lib/styles.css";
|
||||
|
||||
const props = defineProps({
|
||||
value: { type: [String, Object, Array, Number, Boolean], default: '' },
|
||||
height: { type: String, default: '240px' },
|
||||
value: { type: [String, Object, Array, Number, Boolean], default: "" },
|
||||
height: { type: String, default: "240px" },
|
||||
});
|
||||
|
||||
const parsed = computed(() => {
|
||||
const v: any = props.value;
|
||||
if (typeof v === 'string') {
|
||||
if (typeof v === "string") {
|
||||
try {
|
||||
return JSON.parse(v);
|
||||
} catch {
|
||||
@@ -34,10 +34,10 @@ const parsed = computed(() => {
|
||||
return v;
|
||||
});
|
||||
|
||||
const isJson = computed(() => typeof parsed.value === 'object' && parsed.value !== null);
|
||||
const isJson = computed(() => typeof parsed.value === "object" && parsed.value !== null);
|
||||
const displayText = computed(() => {
|
||||
const v: any = props.value;
|
||||
return typeof v === 'string' ? v : JSON.stringify(v, null, 2);
|
||||
return typeof v === "string" ? v : JSON.stringify(v, null, 2);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -54,7 +54,9 @@ const displayText = computed(() => {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin: 0;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-family:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
||||
monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -296,7 +296,8 @@ function loadRoutes(routes: RouteRecordRaw[], parentPath = "") {
|
||||
: `${parentPath}${parentPath.endsWith("/") ? "" : "/"}${route.path}`;
|
||||
|
||||
// 检查是否需要排除
|
||||
if (excludedRoutes.value.includes(route.path) || isExternal(route.path) || route.meta?.hidden) return;
|
||||
if (excludedRoutes.value.includes(route.path) || isExternal(route.path) || route.meta?.hidden)
|
||||
return;
|
||||
|
||||
// 处理有子路由的情况
|
||||
if (route.children) {
|
||||
@@ -316,7 +317,7 @@ function loadRoutes(routes: RouteRecordRaw[], parentPath = "") {
|
||||
}
|
||||
// 递归处理子路由
|
||||
loadRoutes(route.children, path);
|
||||
}
|
||||
}
|
||||
// 处理没有子路由但有title的情况
|
||||
else if (route.meta?.title) {
|
||||
const title = route.meta.title === "dashboard" ? "首页" : route.meta.title;
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
<div v-for="(item, index) in noticeList" :key="index" class="w-400px py-3">
|
||||
<div class="flex-y-center">
|
||||
<el-tag :type="item.notice_type === '1' ? 'primary' : 'warning'">
|
||||
{{ item.notice_type === '1' ? '通知' : '公告' }}
|
||||
{{ item.notice_type === "1" ? "通知" : "公告" }}
|
||||
</el-tag>
|
||||
|
||||
<!-- truncated: 超出部分省略 -->
|
||||
<el-text size="small" class="w-200px cursor-pointer !ml-2 !flex-1" truncated >
|
||||
<el-text size="small" class="w-200px cursor-pointer !ml-2 !flex-1" truncated>
|
||||
{{ item.notice_content }}
|
||||
</el-text>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
|
||||
|
||||
<div class="flex-x-between">
|
||||
<el-link type="primary" underline="never" @click="handleViewMoreNotice">
|
||||
<span class="text-xs">查看更多</span>
|
||||
@@ -36,7 +36,12 @@
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
</el-link>
|
||||
<el-link v-if="noticeList.length > 0" type="primary" underline="never" @click="handleMarkAllAsRead">
|
||||
<el-link
|
||||
v-if="noticeList.length > 0"
|
||||
type="primary"
|
||||
underline="never"
|
||||
@click="handleMarkAllAsRead"
|
||||
>
|
||||
<span class="text-xs">全部已读</span>
|
||||
</el-link>
|
||||
</div>
|
||||
@@ -50,7 +55,12 @@
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
<el-dialog v-model="noticeDialogVisible" :title="noticeDetail?.notice_title ?? '通知详情'" width="800px" custom-class="notification-detail">
|
||||
<el-dialog
|
||||
v-model="noticeDialogVisible"
|
||||
:title="noticeDetail?.notice_title ?? '通知详情'"
|
||||
width="800px"
|
||||
custom-class="notification-detail"
|
||||
>
|
||||
<div v-if="noticeDetail" class="p-x-20px">
|
||||
<div class="flex-y-center mb-16px text-13px text-color-secondary">
|
||||
<span class="flex-y-center">
|
||||
@@ -89,7 +99,7 @@ const noticeDetail = ref<NoticeTable | null>(null);
|
||||
* 获取我的通知公告
|
||||
*/
|
||||
async function featchMyNotice() {
|
||||
await noticeStore.getNotice()
|
||||
await noticeStore.getNotice();
|
||||
noticeList.value = noticeStore.noticeList;
|
||||
}
|
||||
|
||||
@@ -100,7 +110,9 @@ function handleViewMoreNotice() {
|
||||
|
||||
// 全部已读:将这些公告禁用(status=false),刷新后不再出现
|
||||
function handleMarkAllAsRead() {
|
||||
const ids = noticeList.value.map((item) => item.id).filter((id): id is number => id !== undefined);
|
||||
const ids = noticeList.value
|
||||
.map((item) => item.id)
|
||||
.filter((id): id is number => id !== undefined);
|
||||
NoticeAPI.batchAvailableNotice({ ids, status: false }).then(async () => {
|
||||
await noticeStore.getNotice();
|
||||
noticeList.value = noticeStore.noticeList;
|
||||
@@ -110,8 +122,6 @@ function handleMarkAllAsRead() {
|
||||
onMounted(() => {
|
||||
featchMyNotice();
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -49,8 +49,8 @@ const calculateWidth = () => {
|
||||
|
||||
// 计算最终宽度
|
||||
const finalWidth = computed(() => {
|
||||
const widthNum = typeof props.width === 'number' ? props.width : Number(props.width);
|
||||
const minWidthNum = typeof props.minWidth === 'number' ? props.minWidth : Number(props.minWidth);
|
||||
const widthNum = typeof props.width === "number" ? props.width : Number(props.width);
|
||||
const minWidthNum = typeof props.minWidth === "number" ? props.minWidth : Number(props.minWidth);
|
||||
return widthNum || operationWidth.value || minWidthNum;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- 布局大小 -->
|
||||
<!-- 布局大小 -->
|
||||
<template>
|
||||
<el-tooltip :content="t('sizeSelect.tooltip')" effect="dark" placement="bottom">
|
||||
<el-dropdown trigger="click" @command="handleSizeChange">
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<!-- 弹出框内容 -->
|
||||
<div ref="popoverContentRef">
|
||||
<!-- 表单 -->
|
||||
<el-form ref="formRef" :model="queryParams" :inline="true" >
|
||||
<el-form ref="formRef" :model="queryParams" :inline="true">
|
||||
<template v-for="item in selectConfig.formItems" :key="item.prop">
|
||||
<el-form-item :label="item.label" :prop="item.prop">
|
||||
<!-- Input 输入框 -->
|
||||
@@ -156,7 +156,9 @@ export interface ISelectConfig<T = any> {
|
||||
// 占位符
|
||||
placeholder?: string;
|
||||
// popover组件属性
|
||||
popover?: Partial<Omit<PopoverProps, "visible" | "v-model:visible">> & { width?: number | string };
|
||||
popover?: Partial<Omit<PopoverProps, "visible" | "v-model:visible">> & {
|
||||
width?: number | string;
|
||||
};
|
||||
// 列表的网络请求函数(需返回promise)
|
||||
indexAction: (_queryParams: T) => Promise<any>;
|
||||
// 主键名(跨页选择必填,默认为id)
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
:preview-teleported="true"
|
||||
@click.stop="handleImageClick"
|
||||
/>
|
||||
<el-icon
|
||||
v-if="!props.disabled"
|
||||
class="single-upload__delete-btn"
|
||||
<el-icon
|
||||
v-if="!props.disabled"
|
||||
class="single-upload__delete-btn"
|
||||
@click.stop="handleDelete"
|
||||
>
|
||||
<CircleCloseFilled />
|
||||
@@ -49,7 +49,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { UploadRawFile, UploadRequestOptions, ElMessage, type UploadUserFile } from "element-plus";
|
||||
import ParamsAPI from '@/api/module_system/params';
|
||||
import ParamsAPI from "@/api/module_system/params";
|
||||
|
||||
const props = defineProps({
|
||||
/**
|
||||
@@ -96,7 +96,7 @@ const props = defineProps({
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 是否禁用
|
||||
*/
|
||||
@@ -104,7 +104,7 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 是否显示提示信息
|
||||
*/
|
||||
@@ -112,7 +112,7 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 提示文本
|
||||
*/
|
||||
@@ -120,7 +120,7 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 是否启用图片预览功能
|
||||
*/
|
||||
@@ -143,10 +143,12 @@ watch(
|
||||
() => modelValue.value,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
internalFileList.value = [{
|
||||
name: newVal.split('/').pop() || 'image',
|
||||
url: newVal
|
||||
}];
|
||||
internalFileList.value = [
|
||||
{
|
||||
name: newVal.split("/").pop() || "image",
|
||||
url: newVal,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
internalFileList.value = [];
|
||||
}
|
||||
@@ -171,10 +173,10 @@ watch(
|
||||
* 定义组件触发的事件
|
||||
*/
|
||||
const emit = defineEmits<{
|
||||
(e: 'success', fileInfo: UploadFilePath): void;
|
||||
(e: 'error', error: any): void;
|
||||
(e: 'input', value: string): void;
|
||||
(e: 'update:modelValue', value: string): void;
|
||||
(e: "success", fileInfo: UploadFilePath): void;
|
||||
(e: "error", error: any): void;
|
||||
(e: "input", value: string): void;
|
||||
(e: "update:modelValue", value: string): void;
|
||||
}>();
|
||||
|
||||
/**
|
||||
@@ -218,7 +220,7 @@ async function handleUpload(options: UploadRequestOptions) {
|
||||
try {
|
||||
const file = options.file;
|
||||
const formData = new FormData();
|
||||
|
||||
|
||||
formData.append(props.name, file);
|
||||
|
||||
// 处理附加参数
|
||||
@@ -234,7 +236,7 @@ async function handleUpload(options: UploadRequestOptions) {
|
||||
onSuccess(fileInfo);
|
||||
return fileInfo;
|
||||
} else {
|
||||
const errorMsg = response.data.msg || '上传失败';
|
||||
const errorMsg = response.data.msg || "上传失败";
|
||||
ElMessage.error(errorMsg);
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
@@ -259,9 +261,14 @@ function handleDelete() {
|
||||
function handleImageClick(event: Event) {
|
||||
// 阻止事件冒泡
|
||||
event.stopPropagation();
|
||||
|
||||
|
||||
// 如果启用了预览功能,则触发预览
|
||||
if (props.enablePreview && internalFileList.value && internalFileList.value.length > 0 && internalFileList.value[0].url) {
|
||||
if (
|
||||
props.enablePreview &&
|
||||
internalFileList.value &&
|
||||
internalFileList.value.length > 0 &&
|
||||
internalFileList.value[0].url
|
||||
) {
|
||||
// Element Plus的el-image组件会自动处理preview-src-list的预览功能
|
||||
// 这里只需要阻止冒泡即可
|
||||
}
|
||||
@@ -274,17 +281,19 @@ function handleImageClick(event: Event) {
|
||||
*/
|
||||
const onSuccess = (fileInfo: UploadFilePath) => {
|
||||
// 更新绑定的值为文件URL
|
||||
const newFileList = [{
|
||||
name: fileInfo.file_name,
|
||||
url: fileInfo.file_url
|
||||
}];
|
||||
|
||||
const newFileList = [
|
||||
{
|
||||
name: fileInfo.file_name,
|
||||
url: fileInfo.file_url,
|
||||
},
|
||||
];
|
||||
|
||||
internalFileList.value = newFileList;
|
||||
|
||||
|
||||
// 触发事件
|
||||
emit('success', fileInfo);
|
||||
emit('input', fileInfo.file_url);
|
||||
emit('update:modelValue', fileInfo.file_url);
|
||||
emit("success", fileInfo);
|
||||
emit("input", fileInfo.file_url);
|
||||
emit("update:modelValue", fileInfo.file_url);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -292,8 +301,8 @@ const onSuccess = (fileInfo: UploadFilePath) => {
|
||||
*/
|
||||
const onError = (error: any) => {
|
||||
console.error("图片上传失败:", error);
|
||||
ElMessage.error('图片上传失败,请重试');
|
||||
emit('error', error);
|
||||
ElMessage.error("图片上传失败,请重试");
|
||||
emit("error", error);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -308,7 +317,7 @@ const onError = (error: any) => {
|
||||
position: relative;
|
||||
width: v-bind("props.style.width ?? '150px'");
|
||||
height: v-bind("props.style.height ?? '150px'");
|
||||
|
||||
|
||||
&:hover {
|
||||
.single-upload__delete-btn {
|
||||
display: block;
|
||||
@@ -337,7 +346,7 @@ const onError = (error: any) => {
|
||||
color: #ff4500;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&__add-btn {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
|
||||
@@ -50,10 +50,10 @@ const FileAPI = {
|
||||
// 这里应该是实际的文件上传逻辑
|
||||
// 暂时返回一个mock数据
|
||||
return Promise.resolve({
|
||||
url: 'https://example.com/image.jpg',
|
||||
name: file.name
|
||||
url: "https://example.com/image.jpg",
|
||||
name: file.name,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
defineProps({
|
||||
|
||||
Reference in New Issue
Block a user