style: 移除多余空行并统一代码格式

修复代码中的多余空行问题,统一字符串引号使用双引号,调整对象和数组的格式,优化代码缩进和换行,提升代码可读性和一致性
This commit is contained in:
zhangtao
2025-11-14 01:45:22 +08:00
parent 66c345d590
commit d588abb386
100 changed files with 9192 additions and 5181 deletions
+25 -19
View File
@@ -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");
}
+44 -32
View File
@@ -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>
+141 -9
View File
@@ -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>
+63 -21
View File
@@ -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[];
/** 初始值 */
+15 -5
View File
@@ -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))
);
}
}
+36 -37
View File
@@ -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>
+9 -14
View File
@@ -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>
+23 -30
View File
@@ -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>
+44 -20
View File
@@ -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>
+11 -9
View File
@@ -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>
+3 -2
View File
@@ -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;
+19 -9
View File
@@ -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 -1
View File
@@ -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;
+3 -3
View File
@@ -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({