🎯 将要执行:
-
+
跳转到:
{{ response.action.pageName }}
并搜索:
- {{ response.action.query }}
+ {{ response.action.query }}
-
+
跳转至:
{{ response.action.pageName }}
并搜索:
- {{ response.action.query }}
+ {{ response.action.query }}
-
-
+
+
执行:
{{ response.action.functionCall.name }}
-
+
执行:
{{ response.action.functionName }}
@@ -107,14 +107,14 @@
-
+
diff --git a/frontend/new-web/src/components/Breadcrumb/index.vue b/frontend/new-web/src/components/Breadcrumb/index.vue
index 3137c682..7d62bc1b 100644
--- a/frontend/new-web/src/components/Breadcrumb/index.vue
+++ b/frontend/new-web/src/components/Breadcrumb/index.vue
@@ -1,6 +1,6 @@
-
-
+
+
{{ translateRouteTitle(item.meta.title) }}
-
-
+
+
-
-
diff --git a/frontend/new-web/src/components/Crud/CrudContent.vue b/frontend/new-web/src/components/Crud/CrudContent.vue
deleted file mode 100644
index 2c34c546..00000000
--- a/frontend/new-web/src/components/Crud/CrudContent.vue
+++ /dev/null
@@ -1,1141 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ (col.selectList ?? {})[scope.row[col.prop]] }}
-
-
-
-
-
-
- {{ scope.row[col.prop] }}
-
-
-
-
-
-
-
- 0 &&
- handleModify(col.prop, scope.row[col.prop], scope.row)
- "
- />
-
-
-
-
-
-
-
-
-
-
-
- {{ `${col.priceFormat ?? "¥"}${scope.row[col.prop]}` }}
-
-
-
-
- {{ scope.row[col.prop] }}%
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{
- scope.row[col.prop]
- ? useDateFormat(
- scope.row[col.prop],
- col.dateFormat ?? "YYYY-MM-DD HH:mm:ss"
- ).value
- : ""
- }}
-
-
-
-
-
-
- {{ btn.text }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 确 定
- 取 消
-
-
-
-
-
-
-
-
-
-
-
-
-
- 将文件拖到此处,或
- 点击上传
-
-
-
- *.xlsx / *.xls
-
- 下载模板
-
-
-
-
-
-
-
-
-
-
-
- 确 定
-
- 取 消
-
-
-
-
-
-
-
-
-
diff --git a/frontend/new-web/src/components/Crud/CrudFormModal.vue b/frontend/new-web/src/components/Crud/CrudFormModal.vue
deleted file mode 100644
index 91d03ffb..00000000
--- a/frontend/new-web/src/components/Crud/CrudFormModal.vue
+++ /dev/null
@@ -1,243 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- {{ item?.label || "" }}
-
-
-
- :
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 确 定
- {{ !formDisable ? "取 消" : "关闭" }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ item?.label || "" }}
-
-
-
- :
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 确 定
- {{ !formDisable ? "取 消" : "关闭" }}
-
-
-
-
-
-
-
-
-
diff --git a/frontend/new-web/src/components/Crud/CrudLayout.vue b/frontend/new-web/src/components/Crud/CrudLayout.vue
deleted file mode 100644
index 06a3bba9..00000000
--- a/frontend/new-web/src/components/Crud/CrudLayout.vue
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
diff --git a/frontend/new-web/src/components/Crud/CrudSearch.vue b/frontend/new-web/src/components/Crud/CrudSearch.vue
deleted file mode 100644
index c3534267..00000000
--- a/frontend/new-web/src/components/Crud/CrudSearch.vue
+++ /dev/null
@@ -1,428 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ button.text }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ item?.label || "" }}
-
-
-
- :
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ button.text }}
-
-
-
-
-
-
-
-
- 搜索
-
-
-
- 重置
-
-
-
-
-
-
- {{ isExpand ? "收起" : "展开" }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/new-web/src/components/Crud/CrudToolbarActions.vue b/frontend/new-web/src/components/Crud/CrudToolbarActions.vue
deleted file mode 100644
index f0d403e9..00000000
--- a/frontend/new-web/src/components/Crud/CrudToolbarActions.vue
+++ /dev/null
@@ -1,127 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- {{ btn.text }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ btn.text }}
-
-
-
-
-
-
- {{ btn.text }}
-
-
-
-
-
-
diff --git a/frontend/new-web/src/components/Crud/CrudToolbarLeft.vue b/frontend/new-web/src/components/Crud/CrudToolbarLeft.vue
deleted file mode 100644
index 1783dc2d..00000000
--- a/frontend/new-web/src/components/Crud/CrudToolbarLeft.vue
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
-
-
diff --git a/frontend/new-web/src/components/Crud/crudColsToArtTableColumns.ts b/frontend/new-web/src/components/Crud/crudColsToArtTableColumns.ts
deleted file mode 100644
index bb0611f1..00000000
--- a/frontend/new-web/src/components/Crud/crudColsToArtTableColumns.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import dayjs from "dayjs";
-import type { ColumnOption } from "@/types/component";
-import type { IContentConfig } from "./types";
-
-type CrudCol = IContentConfig["cols"][number];
-
-/** 不参与透传给 ElTableColumn 的 Crud 扩展字段 */
-const CRUD_META_KEYS = new Set([
- "templet",
- "slotName",
- "selectList",
- "dateFormat",
- "operat",
- "imageWidth",
- "imageHeight",
- "priceFormat",
- "activeValue",
- "inactiveValue",
- "activeText",
- "inactiveText",
- "inputType",
- "initFn",
- "disabled",
-]);
-
-function passthroughColumn(col: CrudCol): ColumnOption {
- const raw = col as Record
;
- const out: Record = {};
- for (const key of Object.keys(raw)) {
- if (CRUD_META_KEYS.has(key)) continue;
- if (key === "show") continue;
- out[key] = raw[key];
- }
- if (col.show !== undefined) {
- out.checked = col.show;
- out.visible = col.show;
- }
- return out as ColumnOption;
-}
-
-/**
- * 将 Crud `cols` 转为 ArtTable / ArtTableHeader 使用的 `ColumnOption[]`。
- * - `templet: custom` → `useSlot`
- * - `templet: date` → `formatter`(dayjs)
- * - `templet: list` → `formatter`(selectList)
- * - 其余带 `templet` 且无法在 Art 列模型表达的,仍走 `useSlot`,由页面提供同名插槽
- */
-export function mapCrudColsToArtColumns(items: CrudCol[]): ColumnOption[] {
- const out: ColumnOption[] = [];
-
- for (const col of items) {
- if (col.show === false) continue;
-
- if (col.type === "selection" || col.type === "index") {
- out.push(passthroughColumn(col));
- continue;
- }
-
- const base = passthroughColumn(col);
-
- if (col.templet === "custom" && col.prop) {
- out.push({
- ...base,
- useSlot: true,
- slotName: col.slotName ?? col.prop,
- });
- continue;
- }
-
- if (col.templet === "date" && col.prop) {
- const fmt = col.dateFormat ?? "YYYY-MM-DD HH:mm:ss";
- const prop = col.prop;
- out.push({
- ...base,
- formatter: (row: Record) => {
- const v = row[prop];
- return v ? dayjs(v as string | number | Date).format(fmt) : "";
- },
- });
- continue;
- }
-
- if (col.templet === "list" && col.prop && col.selectList) {
- const prop = col.prop;
- const sl = col.selectList as Record;
- out.push({
- ...base,
- formatter: (row: Record) =>
- sl[row[prop] as string] != null ? String(sl[row[prop] as string]) : "",
- });
- continue;
- }
-
- if (!col.templet) {
- out.push(base);
- continue;
- }
-
- /* 其余 templet 优先走单元格展示兜底(复杂控件请改用 templet: custom + 插槽,或继续使用 legacy 表格) */
- if (col.prop) {
- out.push({
- ...base,
- formatter: (row: Record) => row[col.prop!] ?? "",
- });
- } else {
- out.push(base);
- }
- }
-
- return out;
-}
diff --git a/frontend/new-web/src/components/Crud/crudHelpers.ts b/frontend/new-web/src/components/Crud/crudHelpers.ts
deleted file mode 100644
index 8f0cadd3..00000000
--- a/frontend/new-web/src/components/Crud/crudHelpers.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { IObject, IComponentType, ISearchComponent } from "./types";
-import { markRaw } from "vue";
-import {
- ElCascader,
- ElCheckbox,
- ElCheckboxGroup,
- ElDatePicker,
- ElInput,
- ElInputNumber,
- ElInputTag,
- ElOption,
- ElRadio,
- ElRadioGroup,
- ElSelect,
- ElSwitch,
- ElText,
- ElTimePicker,
- ElTimeSelect,
- ElTreeSelect,
-} from "element-plus";
-import InputTag from "@/components/InputTag/index.vue";
-import IconSelect from "@/components/IconSelect/index.vue";
-import DatePicker from "@/components/DatePicker/index.vue";
-
-export const getTooltipProps = (tips: string | IObject) => {
- return typeof tips === "string" ? { content: tips } : tips;
-};
-
-export const modalComponentMap = new Map([
- ["input", markRaw(ElInput)],
- ["select", markRaw(ElSelect)],
- ["switch", markRaw(ElSwitch)],
- ["cascader", markRaw(ElCascader)],
- ["input-number", markRaw(ElInputNumber)],
- ["input-tag", markRaw(InputTag)],
- ["time-picker", markRaw(ElTimePicker)],
- ["time-select", markRaw(ElTimeSelect)],
- ["date-picker", markRaw(ElDatePicker)],
- ["tree-select", markRaw(ElTreeSelect)],
- ["custom-tag", markRaw(InputTag)],
- ["text", markRaw(ElText)],
- ["radio", markRaw(ElRadioGroup)],
- ["checkbox", markRaw(ElCheckboxGroup)],
- ["icon-select", markRaw(IconSelect)],
- ["custom", ""],
-]);
-
-export const searchComponentMap = new Map([
- ["input", markRaw(ElInput)],
- ["select", markRaw(ElSelect)],
- ["cascader", markRaw(ElCascader)],
- ["input-number", markRaw(ElInputNumber)],
- ["date-picker", markRaw(DatePicker)],
- ["time-picker", markRaw(ElTimePicker)],
- ["time-select", markRaw(ElTimeSelect)],
- ["tree-select", markRaw(ElTreeSelect)],
- ["input-tag", markRaw(ElInputTag)],
- ["custom-tag", markRaw(InputTag)],
-]);
-
-export const childrenMap = new Map([
- ["select", markRaw(ElOption)],
- ["radio", markRaw(ElRadio)],
- ["checkbox", markRaw(ElCheckbox)],
-]);
diff --git a/frontend/new-web/src/components/Crud/crudSearchFormToArt.ts b/frontend/new-web/src/components/Crud/crudSearchFormToArt.ts
deleted file mode 100644
index 7d5f6d73..00000000
--- a/frontend/new-web/src/components/Crud/crudSearchFormToArt.ts
+++ /dev/null
@@ -1,240 +0,0 @@
-import type { SearchFormItem } from "@/components/Core/forms/art-search-bar/index.vue";
-import type { IFormItems, ISearchComponent } from "./types";
-import { unref } from "vue";
-
-/**
- * 与 legacy `CrudSearch` onMounted 赋值逻辑一致,需在挂载 ArtSearchBar 之前同步执行,
- * 以便 ArtSearchBar 内部 `initialModelValue` 快照正确。
- */
-export function createCrudSearchInitialParams(
- items: IFormItems,
- getCustomComponent: (componentName: string) => unknown
-): Record {
- const q: Record = {};
- for (const item of items) {
- if (getCustomComponent(item.type ?? "")) {
- q[item.prop] = item.initialValue ?? null;
- } else if (["input-tag", "custom-tag", "cascader"].includes(item?.type ?? "")) {
- q[item.prop] = Array.isArray(item.initialValue) ? item.initialValue : [];
- } else if (
- item.type === "date-picker" &&
- String(item.attrs?.type ?? "")
- .toLowerCase()
- .includes("range")
- ) {
- q[item.prop] = item.initialValue ?? [];
- } else if (item.type === "select") {
- q[item.prop] = item.initialValue !== undefined ? item.initialValue : null;
- } else if (item.type === "input-number") {
- q[item.prop] = item.initialValue ?? null;
- } else {
- q[item.prop] = item.initialValue ?? "";
- }
- }
- return q;
-}
-
-export type MapCrudSearchToArtOptions = {
- customComponents?: Record;
- /** 筛选控件最大宽度,透传到各 `props.style.maxWidth`(`false` 表示不限制) */
- fieldMaxWidth?: string | false;
-};
-
-function artSpanFromCol(col: unknown): number | undefined {
- if (col && typeof col === "object" && "span" in col) {
- const s = (col as { span?: number }).span;
- return typeof s === "number" ? s : undefined;
- }
- return undefined;
-}
-
-function mapDatePickerType(attrs: IFormItems[number]["attrs"]): string {
- const t = String(attrs?.type ?? "date").toLowerCase();
- if (t.includes("datetimerange")) return "datetimerange";
- if (t.includes("daterange")) return "daterange";
- if (t.includes("datetime")) return "datetime";
- return "date";
-}
-
-/**
- * 将 Crud `ISearchConfig.formItems` 转为 ArtSearchBar `items`。
- */
-export function mapCrudSearchFormItemsToArt(
- formItems: IFormItems,
- options?: MapCrudSearchToArtOptions
-): SearchFormItem[] {
- const { customComponents, fieldMaxWidth } = options ?? {};
-
- const maxStyle =
- fieldMaxWidth === undefined || fieldMaxWidth === false
- ? undefined
- : { maxWidth: fieldMaxWidth };
-
- return formItems.map((item) => {
- const attrs = item.attrs ?? {};
- const prevStyle = attrs.style && typeof attrs.style === "object" ? attrs.style : {};
- const baseProps = {
- ...attrs,
- ...(maxStyle ? { style: { ...prevStyle, ...maxStyle } } : {}),
- };
-
- const custom = item.type ? customComponents?.[item.type as string] : undefined;
- if (custom) {
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span: artSpanFromCol(item.col),
- render: custom,
- props: baseProps,
- };
- }
-
- const span = artSpanFromCol(item.col);
- const optsRaw = item.options ? unref(item.options) : undefined;
- const selectOptions = Array.isArray(optsRaw)
- ? optsRaw.map((o) => ({ label: o.label, value: o.value, ...o }))
- : undefined;
-
- switch (item.type) {
- case "input":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "input",
- props: baseProps,
- };
- case "select":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "select",
- props: { ...baseProps, ...(selectOptions ? { options: selectOptions } : {}) },
- };
- case "input-number":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "number",
- props: baseProps,
- };
- case "cascader":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "cascader",
- props: baseProps,
- };
- case "tree-select":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "treeselect",
- props: baseProps,
- };
- case "date-picker": {
- const artType = mapDatePickerType(item.attrs);
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: artType,
- props: baseProps,
- };
- }
- case "time-picker":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "timepicker",
- props: baseProps,
- };
- case "time-select":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "timeselect",
- props: baseProps,
- };
- case "input-tag":
- case "custom-tag":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "inputTag",
- props: baseProps,
- };
- case "radio":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "radiogroup",
- props: { ...baseProps, ...(selectOptions ? { options: selectOptions } : {}) },
- };
- case "checkbox":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "checkboxgroup",
- props: { ...baseProps, ...(selectOptions ? { options: selectOptions } : {}) },
- };
- case "switch":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "switch",
- props: baseProps,
- };
- case "rate":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "rate",
- props: baseProps,
- };
- case "slider":
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "slider",
- props: baseProps,
- };
- default:
- return {
- key: item.prop,
- label: item.label,
- hidden: item.hidden,
- span,
- type: "input",
- props: baseProps,
- };
- }
- });
-}
diff --git a/frontend/new-web/src/components/Crud/index.ts b/frontend/new-web/src/components/Crud/index.ts
deleted file mode 100644
index 5dfa8dfd..00000000
--- a/frontend/new-web/src/components/Crud/index.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * CRUD 列表模块(目录名 `CURD`)。组件与组合式函数统一使用 **Crud** 拼写(勿写 Curd)。
- */
-export { default as CrudLayout } from "./CrudLayout.vue";
-export { default as CrudArtTableDemo } from "./CrudArtTableDemo.vue";
-export { default as CrudSearch } from "./CrudSearch.vue";
-export { default as CrudContent } from "./CrudContent.vue";
-export { default as CrudFormModal } from "./CrudFormModal.vue";
-export { default as CrudExportModal } from "./CrudExportModal.vue";
-export { default as CrudImportModal } from "./CrudImportModal.vue";
-export { default as CrudToolbarLeft } from "./CrudToolbarLeft.vue";
-export { default as CrudToolbarActions } from "./CrudToolbarActions.vue";
-
-export { useCrudList } from "./useCrudList";
-export { useCrudArtTableDemo } from "./useCrudArtTableDemo";
-export type { DemoUserRow } from "./useCrudArtTableDemo";
-
-export { childrenMap, getTooltipProps, modalComponentMap, searchComponentMap } from "./crudHelpers";
-
-export * from "./types";
diff --git a/frontend/new-web/src/components/Crud/useCrudArtTableDemo.ts b/frontend/new-web/src/components/Crud/useCrudArtTableDemo.ts
deleted file mode 100644
index 3830c176..00000000
--- a/frontend/new-web/src/components/Crud/useCrudArtTableDemo.ts
+++ /dev/null
@@ -1,483 +0,0 @@
-/**
- * CrudArtTableDemo(Art 栈)演示:ArtSearchBar + ArtTableHeader + ArtTable + useTable。
- */
-import { h, ref, computed } from "vue";
-import type { TableError } from "@/utils/table/tableUtils";
-import { ElMessage, ElMessageBox } from "element-plus";
-import { useTable } from "@/hooks/core/useTable";
-import { fetchGetUserList } from "@/api/system-manage";
-import { ACCOUNT_TABLE_DATA } from "@/mock/temp/formData";
-import { getColumnKey } from "@/hooks/core/useTableColumns";
-
-export interface DemoUserRow {
- id?: number;
- nickName?: string;
- userName: string;
- userGender?: string;
- userPhone?: string;
- userEmail?: string;
- avatar?: string;
- status?: string;
- department?: string;
- score?: number;
-}
-
-export function useCrudArtTableDemo() {
- const selectedRows = ref([]);
- const tableRef = ref();
- const searchBarRef = ref();
- const phoneSearch = ref("");
-
- const tableConfig = ref({
- height: "100%",
- fixedHeight: false,
- });
-
- const computedTableHeight = computed(() => (tableConfig.value.fixedHeight ? "500px" : ""));
-
- const searchFormState = ref({
- name: "",
- phone: "",
- status: "1",
- department: "",
- daterange: ["2025-01-01", "2025-02-10"] as [string, string],
- });
-
- const rules = {
- name: [{ required: true, message: "请输入用户名", trigger: "blur" }],
- phone: [
- { required: true, message: "请输入手机号", trigger: "blur" },
- { pattern: /^1[3456789]\d{9}$/, message: "请输入正确的手机号", trigger: "blur" },
- ],
- };
-
- const searchItems = computed(() => [
- {
- key: "name",
- label: "用户名",
- type: "input",
- props: { placeholder: "请输入用户名" },
- },
- {
- key: "phone",
- label: "手机号",
- type: "input",
- props: { placeholder: "请输入手机号", maxlength: "11" },
- },
- {
- key: "status",
- label: "状态",
- type: "select",
- options: [
- { label: "全部", value: "" },
- { label: "在线", value: "1" },
- { label: "离线", value: "2" },
- { label: "异常", value: "3" },
- { label: "注销", value: "4" },
- ],
- },
- {
- key: "department",
- label: "部门",
- type: "select",
- options: [
- { label: "全部", value: "" },
- { label: "技术部", value: "技术部" },
- { label: "产品部", value: "产品部" },
- { label: "运营部", value: "运营部" },
- { label: "市场部", value: "市场部" },
- { label: "设计部", value: "设计部" },
- ],
- },
- {
- key: "daterange",
- label: "日期范围",
- type: "daterange",
- props: {
- type: "daterange",
- startPlaceholder: "开始日期",
- endPlaceholder: "结束日期",
- valueFormat: "YYYY-MM-DD",
- },
- },
- ]);
-
- const USER_STATUS_CONFIG = {
- "1": { type: "success" as const, text: "在线" },
- "2": { type: "info" as const, text: "离线" },
- "3": { type: "warning" as const, text: "异常" },
- "4": { type: "danger" as const, text: "注销" },
- } as const;
-
- const getUserStatusConfig = (status: string) =>
- USER_STATUS_CONFIG[status as keyof typeof USER_STATUS_CONFIG] ?? {
- type: "info" as const,
- text: "未知",
- };
-
- const exportColumns = computed(() => ({
- userName: { title: "用户名", width: 15 },
- userEmail: { title: "邮箱", width: 20 },
- userPhone: { title: "手机号", width: 15 },
- userGender: { title: "性别", width: 10 },
- department: { title: "部门", width: 15 },
- status: {
- title: "状态",
- width: 10,
- formatter: (value: string) => getUserStatusConfig(value).text,
- },
- }));
-
- const buildSearchParams = (params: typeof searchFormState.value) => {
- const { daterange, ...filtersParams } = params;
- const [startTime, endTime] = Array.isArray(daterange) ? daterange : [null, null];
- return { ...filtersParams, startTime, endTime };
- };
-
- const {
- data,
- loading,
- pagination,
- handleSizeChange,
- handleCurrentChange,
- replaceSearchParams,
- resetSearchParams,
- getData,
- getDataDebounced,
- clearData,
- refreshData,
- refreshCreate,
- refreshUpdate,
- refreshRemove,
- columns,
- columnChecks,
- addColumn,
- removeColumn,
- updateColumn,
- toggleColumn,
- resetColumns,
- reorderColumns,
- getColumnConfig,
- getAllColumns,
- } = useTable({
- core: {
- apiFn: (params) => fetchGetUserList(params),
- apiParams: {
- current: 1,
- size: 20,
- ...searchFormState.value,
- },
- excludeParams: ["daterange"],
- immediate: true,
- columnsFactory: () => [
- { type: "selection", width: 50 },
- { type: "globalIndex", width: 60, label: "序号" },
- {
- prop: "avatar",
- label: "用户信息",
- minWidth: 200,
- useSlot: true,
- useHeaderSlot: true,
- sortable: false,
- },
- {
- prop: "userGender",
- label: "性别",
- sortable: true,
- formatter: (row: unknown) => String((row as DemoUserRow).userGender ?? "未知"),
- },
- {
- prop: "userPhone",
- label: "手机号",
- useHeaderSlot: true,
- sortable: true,
- },
- { prop: "department", label: "部门", sortable: true },
- { prop: "score", label: "评分", useSlot: true, sortable: true },
- { prop: "status", label: "状态", useSlot: true, sortable: true },
- {
- prop: "operation",
- label: "操作",
- width: 190,
- useSlot: true,
- fixed: "right",
- },
- ],
- },
- transform: {
- dataTransformer: (records) => {
- if (!Array.isArray(records)) return [];
- return records.map((item: unknown, index: number) => {
- const row = item as DemoUserRow;
- return {
- ...row,
- avatar: ACCOUNT_TABLE_DATA[index % ACCOUNT_TABLE_DATA.length].avatar,
- department: ["技术部", "产品部", "运营部", "市场部", "设计部"][
- Math.floor(Math.random() * 5)
- ],
- score: Math.floor(Math.random() * 5) + 1,
- status: ["1", "2", "3", "4"][Math.floor(Math.random() * 4)],
- };
- }) as typeof records;
- },
- },
- performance: {
- enableCache: true,
- cacheTime: 5 * 60 * 1000,
- debounceTime: 300,
- maxCacheSize: 100,
- },
- hooks: {
- onError: (error: TableError) => {
- ElMessage.error(error.message);
- },
- },
- debug: {
- enableLog: false,
- logLevel: "info",
- },
- });
-
- const artPagination = computed(() => {
- const p = pagination as unknown as Record;
- return {
- current: Number(p.current ?? p.page_no ?? 1),
- size: Number(p.size ?? p.page_size ?? 10),
- total: Number(p.total ?? 0),
- };
- });
-
- interface SortInfo {
- prop: string;
- order: "ascending" | "descending" | null;
- }
-
- const handleSelectionChange = (selection: DemoUserRow[]) => {
- selectedRows.value = selection;
- };
-
- const handleRowClick = (row: DemoUserRow) => {
- console.log("行点击:", row.userName);
- };
-
- const handleHeaderClick = (column: { label: string; property: string }) => {
- console.log("表头点击:", column.label);
- };
-
- const handleSortChange = (sortInfo: SortInfo) => {
- console.log("排序:", sortInfo.prop, sortInfo.order);
- };
-
- const handleSearch = async () => {
- await searchBarRef.value?.validate?.();
- replaceSearchParams(buildSearchParams(searchFormState.value));
- getData();
- };
-
- const handleReset = () => {
- resetSearchParams();
- };
-
- const handlePhoneSearch = (value: string) => {
- searchFormState.value.phone = value;
- replaceSearchParams(buildSearchParams(searchFormState.value));
- getDataDebounced();
- };
-
- const handleRefresh = () => {
- refreshData();
- };
-
- const handleAdd = () => {
- ElMessage.success("新增用户成功");
- refreshCreate();
- };
-
- const handleEdit = (row: DemoUserRow) => {
- ElMessage.success(`编辑用户 ${row.userName} 成功`);
- setTimeout(() => refreshUpdate(), 1000);
- };
-
- const handleDelete = async (row: DemoUserRow) => {
- try {
- await ElMessageBox.confirm(`确定要删除用户 ${row.userName} 吗?`, "警告", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning",
- });
- ElMessage.success("删除成功");
- setTimeout(() => refreshRemove(), 1000);
- } catch {
- ElMessage.info("已取消删除");
- }
- };
-
- const handleView = (row: DemoUserRow) => {
- ElMessage.info(`查看用户 ${row.userName}`);
- };
-
- const handleBatchDelete = async () => {
- try {
- await ElMessageBox.confirm(
- `确定要删除选中的 ${selectedRows.value.length} 个用户吗?`,
- "警告",
- {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning",
- }
- );
- ElMessage.success(`批量删除 ${selectedRows.value.length} 个用户成功`);
- selectedRows.value = [];
- setTimeout(() => refreshRemove(), 1000);
- } catch {
- ElMessage.info("已取消删除");
- }
- };
-
- const handleExportSuccess = (_filename: string, count: number) => {
- ElMessage.success(`导出 ${count} 条数据成功`);
- };
-
- const handleImportSuccess = (rows: Record[]) => {
- ElMessage.success(`导入 ${rows.length} 条数据成功`);
- refreshCreate();
- };
-
- const handleImportError = (error: Error) => {
- ElMessage.error(`导入失败:${error.message}`);
- };
-
- const handleClearData = () => {
- clearData();
- ElMessage.info("数据已清空");
- };
-
- const handleColumnCommand = (command: string): void => {
- switch (command) {
- case "addColumn": {
- addColumn?.({
- prop: "remark",
- label: "备注",
- width: 150,
- formatter: () => h("span", { style: "color: #999" }, "暂无备注"),
- });
- ElMessage.success('已新增"备注"列');
- break;
- }
- case "batchAddColumns": {
- addColumn?.(
- [
- {
- prop: "remark",
- label: "备注",
- width: 150,
- formatter: () => h("span", { style: "color: #999" }, "暂无备注"),
- },
- {
- prop: "tags",
- label: "标签",
- width: 120,
- formatter: () => h("span", { style: "color: #67c23a" }, "新用户"),
- },
- ],
- 5
- );
- ElMessage.success('已批量新增"备注"和"标签"列');
- break;
- }
- case "toggleColumn": {
- if (getColumnConfig?.("userPhone")) {
- toggleColumn?.("userPhone");
- ElMessage.success("已切换手机号列显示状态");
- }
- break;
- }
- case "batchToggleColumns": {
- toggleColumn?.(["userGender", "userPhone"]);
- ElMessage.success("已批量切换性别和手机号列显示状态");
- break;
- }
- case "removeColumn": {
- removeColumn?.("status");
- ElMessage.success("已删除状态列");
- break;
- }
- case "batchRemoveColumns": {
- removeColumn?.(["status", "score"]);
- ElMessage.success("已批量删除状态和评分列");
- break;
- }
- case "updateColumn": {
- updateColumn?.("userPhone", { label: "联系电话", width: 140 });
- ElMessage.success('手机号列已更新为"联系电话"');
- break;
- }
- case "batchUpdateColumns": {
- updateColumn?.([
- { prop: "userGender", updates: { width: 200, label: "性别-已更新", sortable: false } },
- { prop: "userPhone", updates: { width: 200, label: "手机号-已更新", sortable: false } },
- ]);
- ElMessage.success("已批量更新性别和手机号列");
- break;
- }
- case "reorderColumns": {
- const allCols = getAllColumns?.();
- if (allCols) {
- const genderIndex = allCols.findIndex((col) => getColumnKey(col) === "userGender");
- const phoneIndex = allCols.findIndex((col) => getColumnKey(col) === "userPhone");
- if (genderIndex !== -1 && phoneIndex !== -1) {
- reorderColumns?.(genderIndex, phoneIndex);
- ElMessage.success("已交换性别和手机号列位置");
- }
- }
- break;
- }
- case "resetColumns": {
- resetColumns?.();
- ElMessage.success("已重置所有列配置");
- break;
- }
- default:
- console.warn("未知的列配置命令:", command);
- }
- };
-
- return {
- searchBarRef,
- searchFormState,
- searchItems,
- rules,
- phoneSearch,
- selectedRows,
- tableRef,
- columnChecks,
- loading,
- data,
- artPagination,
- columns,
- computedTableHeight,
- exportColumns,
- getUserStatusConfig,
- handleSearch,
- handleReset,
- handlePhoneSearch,
- handleRefresh,
- handleSelectionChange,
- handleRowClick,
- handleHeaderClick,
- handleSortChange,
- handleSizeChange,
- handleCurrentChange,
- handleAdd,
- handleEdit,
- handleDelete,
- handleView,
- handleBatchDelete,
- handleExportSuccess,
- handleImportSuccess,
- handleImportError,
- handleClearData,
- handleColumnCommand,
- };
-}
diff --git a/frontend/new-web/src/components/Crud/useCrudList.ts b/frontend/new-web/src/components/Crud/useCrudList.ts
deleted file mode 100644
index 518ac6d3..00000000
--- a/frontend/new-web/src/components/Crud/useCrudList.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { ElMessage } from "element-plus";
-import { ref } from "vue";
-import type { IObject, CrudContentInstance, CrudSearchInstance } from "./types";
-
-/**
- * 仅列表查询区 + 表格区时复用:searchRef / contentRef 与查询、重置拉数逻辑。
- * 合并 `CrudSearch` 查询参数与 `CrudContent.getFilterParams()`(表头筛选等),与 usePage 中同名逻辑一致。
- * 业务页可替换手写 `handleQueryClick` / `handleResetClick` + 双 ref,参见 `module_example/demo`。
- */
-export function useCrudList() {
- const searchRef = ref();
- const contentRef = ref();
-
- function handleQueryClick(queryParams: IObject) {
- try {
- const filterParams = contentRef.value?.getFilterParams() || {};
- contentRef.value?.fetchPageData({ ...queryParams, ...filterParams }, true);
- } catch (error) {
- console.error("查询数据失败:", error);
- ElMessage.error("查询数据失败: " + (error instanceof Error ? error.message : String(error)));
- }
- }
-
- function handleResetClick(queryParams: IObject) {
- try {
- const filterParams = contentRef.value?.getFilterParams() || {};
- contentRef.value?.fetchPageData({ ...queryParams, ...filterParams }, true);
- } catch (error) {
- console.error("重置数据失败:", error);
- ElMessage.error("重置数据失败: " + (error instanceof Error ? error.message : String(error)));
- }
- }
-
- /** 弹窗提交后等与「查询」同参刷新:合并搜索条件 + 表头筛选 */
- function refreshList() {
- try {
- const q = searchRef.value?.getQueryParams() ?? {};
- const f = contentRef.value?.getFilterParams() ?? {};
- contentRef.value?.fetchPageData({ ...q, ...f }, true);
- } catch (error) {
- console.error("刷新列表失败:", error);
- ElMessage.error("刷新列表失败: " + (error instanceof Error ? error.message : String(error)));
- }
- }
-
- return {
- searchRef,
- contentRef,
- handleQueryClick,
- handleResetClick,
- refreshList,
- };
-}
diff --git a/frontend/new-web/src/components/DatePicker/index.vue b/frontend/new-web/src/components/DatePicker/index.vue
index 6982b4d0..1c791696 100644
--- a/frontend/new-web/src/components/DatePicker/index.vue
+++ b/frontend/new-web/src/components/DatePicker/index.vue
@@ -1,7 +1,7 @@
-
-
-
-
-
- {{ t("common.skipLabel") }}
+ {{ t("common.skipLabel") }}
-
+
diff --git a/frontend/new-web/src/components/core/forms/art-search-bar/auditSearchFormItems.ts b/frontend/new-web/src/components/core/forms/art-search-bar/auditSearchFormItems.ts
new file mode 100644
index 00000000..cc22bc34
--- /dev/null
+++ b/frontend/new-web/src/components/core/forms/art-search-bar/auditSearchFormItems.ts
@@ -0,0 +1,90 @@
+import type { SearchFormItem } from "./index.vue";
+
+/** 与创建/更新人、时间范围配套的后端查询字段(可与其他业务条件组合) */
+export type AuditSearchFormParams = {
+ created_id?: number | null;
+ updated_id?: number | null;
+ created_time?: string[];
+ updated_time?: string[];
+};
+
+export interface GetAuditSearchFormItemsOptions {
+ /** 栅格列宽,与 ArtSearchBar `span` 一致时建议传相同值,默认 6 */
+ span?: number;
+ createdByLabel?: string;
+ updatedByLabel?: string;
+ createdTimeLabel?: string;
+ updatedTimeLabel?: string;
+ createdByPlaceholder?: string;
+ updatedByPlaceholder?: string;
+ /** 日期时间范围 valueFormat,默认 YYYY-MM-DD HH:mm:ss */
+ valueFormat?: string;
+ rangeSeparator?: string;
+ startPlaceholder?: string;
+ endPlaceholder?: string;
+}
+
+/**
+ * 常见「创建人 / 更新人 / 创建时间 / 更新时间」搜索项,供 ArtSearchBar `items` 使用。
+ * 创建人、更新人需配合 `#created_id`、`#updated_id` 插槽(如 ArtSearchBarWithAudit)。
+ */
+export function getAuditSearchFormItems(
+ options?: GetAuditSearchFormItemsOptions
+): SearchFormItem[] {
+ const span = options?.span ?? 6;
+ const valueFormat = options?.valueFormat ?? "YYYY-MM-DD HH:mm:ss";
+ const rangeSep = options?.rangeSeparator ?? "至";
+ const sp = options?.startPlaceholder ?? "开始";
+ const ep = options?.endPlaceholder ?? "结束";
+
+ return [
+ {
+ label: options?.createdByLabel ?? "创建人",
+ key: "created_id",
+ type: "input",
+ props: {
+ placeholder: options?.createdByPlaceholder ?? "请选择创建人",
+ style: { width: "100%" },
+ },
+ span,
+ },
+ {
+ label: options?.updatedByLabel ?? "更新人",
+ key: "updated_id",
+ type: "input",
+ props: {
+ placeholder: options?.updatedByPlaceholder ?? "请选择更新人",
+ style: { width: "100%" },
+ },
+ span,
+ },
+ {
+ label: options?.createdTimeLabel ?? "创建时间",
+ key: "created_time",
+ type: "datetimerange",
+ props: {
+ style: { width: "100%" },
+ type: "datetimerange",
+ rangeSeparator: rangeSep,
+ startPlaceholder: sp,
+ endPlaceholder: ep,
+ valueFormat,
+ },
+ span,
+ },
+ {
+ label: options?.updatedTimeLabel ?? "更新时间",
+ key: "updated_time",
+ type: "datetimerange",
+ props: {
+ style: { width: "100%" },
+ type: "datetimerange",
+ rangeSeparator: rangeSep,
+ startPlaceholder: sp,
+ endPlaceholder: ep,
+ valueFormat,
+ },
+ span,
+ },
+ ];
+}
diff --git a/frontend/new-web/src/components/core/overlays/EnhancedDialog.vue b/frontend/new-web/src/components/core/modal/art-dialog/index.vue
similarity index 73%
rename from frontend/new-web/src/components/core/overlays/EnhancedDialog.vue
rename to frontend/new-web/src/components/core/modal/art-dialog/index.vue
index 2498c998..bf96b816 100644
--- a/frontend/new-web/src/components/core/overlays/EnhancedDialog.vue
+++ b/frontend/new-web/src/components/core/modal/art-dialog/index.vue
@@ -1,5 +1,5 @@
-
@@ -37,7 +42,7 @@
-
+
diff --git a/frontend/new-web/src/components/core/tables/art-table-header/index.vue b/frontend/new-web/src/components/core/tables/art-table-header/index.vue
index 97eddc1e..e24bb1e6 100644
--- a/frontend/new-web/src/components/core/tables/art-table-header/index.vue
+++ b/frontend/new-web/src/components/core/tables/art-table-header/index.vue
@@ -6,6 +6,7 @@
@@ -49,6 +53,7 @@
+
diff --git a/frontend/new-web/src/components/core/text-effect/art-festival-text-scroll/index.vue b/frontend/new-web/src/components/core/text-effect/art-festival-text-scroll/index.vue
index 704a61c9..27af1494 100644
--- a/frontend/new-web/src/components/core/text-effect/art-festival-text-scroll/index.vue
+++ b/frontend/new-web/src/components/core/text-effect/art-festival-text-scroll/index.vue
@@ -1,32 +1,55 @@
-
+
diff --git a/frontend/new-web/src/config/modules/fastEnter.ts b/frontend/new-web/src/config/modules/fastEnter.ts
index cb638a70..67a0c8dd 100644
--- a/frontend/new-web/src/config/modules/fastEnter.ts
+++ b/frontend/new-web/src/config/modules/fastEnter.ts
@@ -107,13 +107,13 @@ const fastEnterConfig: FastEnterConfig = {
name: "定价",
enabled: true,
order: 4,
- routeName: "Pricing",
+ routeName: "DashboardPricing",
},
{
name: "个人中心",
enabled: true,
order: 5,
- routeName: "UserCenter",
+ routeName: "Profile",
},
{
name: "留言管理",
diff --git a/frontend/new-web/src/config/modules/festival.ts b/frontend/new-web/src/config/modules/festival.ts
index 4bebc975..dd82d40c 100644
--- a/frontend/new-web/src/config/modules/festival.ts
+++ b/frontend/new-web/src/config/modules/festival.ts
@@ -12,6 +12,8 @@
* - image: 烟花图片(需要预先导入)
* - scrollText: 滚动显示的祝福文本
* - count: 烟花播放次数(可选,默认为 3 次)
+ * - skipFireworks: 为 true 时不放烟花,仅显示 scrollText(适合长期顶栏公告)
+ * - scrollText 可使用占位符 {{version}}、{{introduceUrl}}(构建时替换为版本号与演示链接)
*
* ## 注意事项
*
@@ -26,11 +28,37 @@
import { FestivalConfig } from "@/types/config";
// 导入烟花图片(根据需要取消注释)
-// import sd from '@imgs/ceremony/sd.png'
-// import yd from '@imgs/ceremony/yd.png'
+// import sd from "@imgs/ceremony/sd.png";
+// import yd from "@imgs/ceremony/yd.png";
export const festivalConfigList: FestivalConfig[] = [
- // 跨日期示例
+ /**
+ * 全年默认顶栏公告(与多条日期重叠时,区间更短的配置优先,例如上面的五月条)
+ */
+ {
+ name: "系统公告",
+ date: "2026-01-01",
+ endDate: "2026-12-31",
+ image: "",
+ skipFireworks: true,
+ count: 3,
+ scrollText:
+ '🎉 {{version}}版本正式上线!能力全面提升,配套完整交付方案,助力高效开发与商业落地。
👉 立即体验演示',
+ },
+ /**
+ * 五月示例(2026-05-01~05-31 生效,含当前日期则会出现节日滚动条;烟花需当日首次进入且未在同日标记已播)
+ * 上线前请改成真实活动日期与文案;需要自定义烟花图时在配置里 import 并填入 image
+ */
+ // {
+ // name: "五月温馨提示",
+ // date: "2026-05-04",
+ // endDate: "2026-05-05",
+ // image: yd,
+ // count: 3,
+ // scrollText:
+ // "🎉 五月快乐!FastAPI Admin 祝您工作顺利、迭代顺利。本月请关注备份与安全策略,遇到问题可先查看文档或联系运维。",
+ // },
+ // 单日示例(圣诞节):需取消注释并 import 图片
// {
// name: 'v3.0 Sass 升级至 TailwindCSS',
// date: '2025-11-03',
diff --git a/frontend/new-web/src/hooks/core/useCeremony.ts b/frontend/new-web/src/hooks/core/useCeremony.ts
index abeffafc..b159ecc3 100644
--- a/frontend/new-web/src/hooks/core/useCeremony.ts
+++ b/frontend/new-web/src/hooks/core/useCeremony.ts
@@ -46,6 +46,7 @@ import { computed } from "vue";
import { useSettingsStore } from "@/store/modules/setting.store";
import { mittBus } from "@/utils/sys";
import { festivalConfigList } from "@/config/modules/festival";
+import { formatToDate } from "@/utils/common/dateUtil";
/**
* 节日庆祝配置常量
@@ -95,19 +96,32 @@ export function useCeremony() {
return current >= start && current <= end;
};
+ /** 区间越短越「具体」,重叠时优先展示(如五月条优先于全年公告) */
+ const festivalRangeSpanDays = (item: (typeof festivalConfigList)[number]): number => {
+ if (!item.endDate) return 1;
+ const s = new Date(item.date).getTime();
+ const e = new Date(item.endDate).getTime();
+ return Math.max(1, Math.round((e - s) / 86400000) + 1);
+ };
+
/**
- * 获取当前日期对应的节日数据
+ * 获取当前日期对应的节日数据(同日多条命中时取区间最短的一条)
*/
const currentFestivalData = computed(() => {
const currentDate = useDateFormat(new Date(), "YYYY-MM-DD").value;
- return festivalConfigList.find((item) => isDateInRange(currentDate, item.date, item.endDate));
+ const matches = festivalConfigList.filter((item) =>
+ isDateInRange(currentDate, item.date, item.endDate)
+ );
+ if (!matches.length) return undefined;
+ matches.sort((a, b) => festivalRangeSpanDays(a) - festivalRangeSpanDays(b));
+ return matches[0];
});
/**
- * 更新节日日期到 store
+ * 标记「今日已完成烟花流程」,用于 store 按自然日防抖(与节日配置的 date 字段无关)
*/
const updateFestivalDate = () => {
- settingStore.setFestivalDate(currentFestivalData.value?.date || "");
+ settingStore.setFestivalDate(formatToDate(new Date()));
};
/**
@@ -151,10 +165,30 @@ export function useCeremony() {
};
/**
- * 开启节日庆祝
+ * 顶栏纯文案关闭(仅 skipFireworks)持久化 key
+ */
+ const festivalScrollDismissKey = (cur: (typeof festivalConfigList)[number]): string =>
+ `festival_scroll_dismissed_${cur.date}_${cur.endDate ?? ""}`;
+
+ /**
+ * 开启节日庆祝:skipFireworks 时直接显示滚动条;否则走烟花再出字
*/
const openFestival = () => {
- if (!currentFestivalData.value || !isShowFireworks.value) {
+ const cur = currentFestivalData.value;
+ if (!cur) return;
+
+ if (cur.skipFireworks) {
+ if (
+ typeof localStorage !== "undefined" &&
+ localStorage.getItem(festivalScrollDismissKey(cur)) === "1"
+ ) {
+ return;
+ }
+ settingStore.setShowFestivalText(true);
+ return;
+ }
+
+ if (!isShowFireworks.value) {
return;
}
@@ -171,7 +205,15 @@ export function useCeremony() {
fireworksInterval = null;
}
settingStore.setShowFestivalText(false);
- updateFestivalDate();
+ };
+
+ /** 关闭顶栏滚动:skipFireworks 时写入 localStorage,下次不再自动展开 */
+ const closeFestivalScroll = () => {
+ const cur = currentFestivalData.value;
+ settingStore.setShowFestivalText(false);
+ if (cur?.skipFireworks && typeof localStorage !== "undefined") {
+ localStorage.setItem(festivalScrollDismissKey(cur), "1");
+ }
};
return {
@@ -180,5 +222,7 @@ export function useCeremony() {
holidayFireworksLoaded,
currentFestivalData,
isShowFireworks,
+ festivalScrollDismissKey,
+ closeFestivalScroll,
};
}
diff --git a/frontend/new-web/src/layouts/art-screen-lock/index.vue b/frontend/new-web/src/layouts/art-screen-lock/index.vue
index 8d90a343..cfc0e3a1 100644
--- a/frontend/new-web/src/layouts/art-screen-lock/index.vue
+++ b/frontend/new-web/src/layouts/art-screen-lock/index.vue
@@ -20,7 +20,7 @@
-
{{ t("navbar.lock") }}
-
+
-
+
{{ t("lock.unlock") }}
@@ -93,7 +93,7 @@
{{ t("lock.message") }}
-
{{ t("common.back") }}
-
-
+
{{ t("lock.backToLogin") }}
-
-
+
{{ t("lock.entrySystem") }}
-
+
@@ -143,7 +143,7 @@
import { Lock } from "@element-plus/icons-vue";
import type { FormInstance, FormRules } from "element-plus";
import { ElInput } from "element-plus";
-import EnhancedDialog from "@/components/Core/overlays/EnhancedDialog.vue";
+import ArtDialog from "@/components/Core/modal/art-dialog/index.vue";
import { useI18n } from "vue-i18n";
import { useRoute, useRouter } from "vue-router";
import CryptoJS from "crypto-js";
diff --git a/frontend/new-web/src/layouts/art-settings-panel/widget/SettingActions.vue b/frontend/new-web/src/layouts/art-settings-panel/widget/SettingActions.vue
index 8a0e4732..cfad733d 100644
--- a/frontend/new-web/src/layouts/art-settings-panel/widget/SettingActions.vue
+++ b/frontend/new-web/src/layouts/art-settings-panel/widget/SettingActions.vue
@@ -93,7 +93,11 @@ const CONFIG_ITEMS: ConfigItem[] = [
{ comment: "标签页样式", key: "tabStyle" },
{ comment: "自定义圆角", key: "customRadius" },
{ comment: "容器宽度", key: "containerWidth", enumMap: ENUM_MAPS.containerWidth },
- { comment: "节日日期", key: "festivalDate", forceValue: "" },
+ {
+ comment: "上次完成烟花播放的自然日(YYYY-MM-DD),留空可重新触发",
+ key: "festivalDate",
+ forceValue: "",
+ },
];
/**
diff --git a/frontend/new-web/src/locales/langs/en.json b/frontend/new-web/src/locales/langs/en.json
index 128fe780..c3fff7c2 100644
--- a/frontend/new-web/src/locales/langs/en.json
+++ b/frontend/new-web/src/locales/langs/en.json
@@ -384,6 +384,7 @@
},
"password": {
"required": "Please enter Password",
+ "currentRequired": "Please enter your current password",
"min": "The password cannot be less than 6 characters",
"confirm": "Please confirm the password again",
"inconformity": "The two password entries are inconsistent"
@@ -534,10 +535,15 @@
"title": "Dashboard",
"console": "Console",
"analysis": "Analysis",
- "ecommerce": "Ecommerce"
+ "ecommerce": "Ecommerce",
+ "map": "Map",
+ "pricing": "Pricing"
},
"widgets": {
"title": "Components",
+ "cards": "Cards",
+ "banners": "Banners",
+ "charts": "Charts",
"icon": "Icon",
"imageCrop": "Image Crop",
"excel": "Excel Import Export",
@@ -549,17 +555,8 @@
"qrcode": "QR Code",
"drag": "Drag",
"textScroll": "Text Scroll",
- "fireworks": "Fireworks"
- },
- "template": {
- "title": "Template Center",
- "chat": "Chat",
- "cards": "Cards",
- "banners": "Banners",
- "charts": "Charts",
- "map": "Map",
- "calendar": "Calendar",
- "pricing": "Pricing"
+ "fireworks": "Fireworks",
+ "changeLog": "Changelog"
},
"article": {
"title": "Article",
@@ -596,7 +593,7 @@
}
},
"system": {
- "title": "System Settings",
+ "title": "Template System",
"user": "User Manage",
"role": "Role Manage",
"userCenter": "User Center",
diff --git a/frontend/new-web/src/locales/langs/zh.json b/frontend/new-web/src/locales/langs/zh.json
index 72d02350..b0f248cf 100644
--- a/frontend/new-web/src/locales/langs/zh.json
+++ b/frontend/new-web/src/locales/langs/zh.json
@@ -384,6 +384,7 @@
},
"password": {
"required": "请输入密码",
+ "currentRequired": "请输入当前密码",
"min": "密码不能少于6位",
"confirm": "请再次确认密码",
"inconformity": "两次密码输入不一致"
@@ -534,10 +535,15 @@
"title": "仪表盘",
"console": "控制台",
"analysis": "分析页",
- "ecommerce": "电子商务"
+ "ecommerce": "电子商务",
+ "map": "地图",
+ "pricing": "定价"
},
"widgets": {
"title": "组件中心",
+ "cards": "卡片",
+ "banners": "横幅",
+ "charts": "图表",
"icon": "图标",
"imageCrop": "图像裁剪",
"excel": "Excel 导入导出",
@@ -549,17 +555,8 @@
"qrcode": "二维码",
"drag": "拖拽",
"textScroll": "文字滚动",
- "fireworks": "礼花"
- },
- "template": {
- "title": "模板中心",
- "chat": "聊天",
- "cards": "卡片",
- "banners": "横幅",
- "charts": "图表",
- "map": "地图",
- "calendar": "日历",
- "pricing": "定价"
+ "fireworks": "礼花",
+ "changeLog": "更新日志"
},
"article": {
"title": "文章管理",
@@ -596,7 +593,7 @@
}
},
"system": {
- "title": "系统管理",
+ "title": "模板系统",
"user": "用户管理",
"role": "角色管理",
"userCenter": "个人中心",
diff --git a/frontend/new-web/src/mock/upgrade/changeLog.ts b/frontend/new-web/src/mock/upgrade/changeLog.ts
index 3c7b7656..d227bcf8 100644
--- a/frontend/new-web/src/mock/upgrade/changeLog.ts
+++ b/frontend/new-web/src/mock/upgrade/changeLog.ts
@@ -1,4 +1,4 @@
-interface UpgradeLog {
+export interface UpgradeLog {
version: string; // 版本号
title: string; // 更新标题
date: string; // 更新日期
diff --git a/frontend/new-web/src/router/core/MenuProcessor.ts b/frontend/new-web/src/router/core/MenuProcessor.ts
index 4777fbdd..f5ee12c8 100644
--- a/frontend/new-web/src/router/core/MenuProcessor.ts
+++ b/frontend/new-web/src/router/core/MenuProcessor.ts
@@ -8,6 +8,7 @@
*/
import type { AppRouteRecord } from "@/types/router";
+import type { UserInfo } from "@/api/module_system/user";
import { useUserStore } from "@/store/modules/user.store";
import { useAppMode } from "@/hooks/core/useAppMode";
import MenuAPI from "@/api/module_system/menu";
@@ -45,15 +46,18 @@ export class MenuProcessor {
*/
private async processFrontendMenu(): Promise {
const userStore = useUserStore();
- const roles = userStore.info?.roles;
-
let menuList = [...asyncRoutes];
- // 根据角色过滤菜单(仅当能解析出角色 code 时才过滤;否则勿用空数组误杀全部 meta.roles)
+ // 超管不做前端 meta.roles 过滤,避免与接口未返回 code 或编码不一致时整棵模板路由被误删
+ if (userStore.info?.is_superuser) {
+ return this.filterEmptyMenus(menuList);
+ }
+
+ const roles = userStore.info?.roles;
+
+ // 根据角色过滤菜单(仅当能解析出角色标识时才过滤;否则勿用空数组误杀全部 meta.roles)
if (roles && roles.length > 0) {
- const roleCodes = roles
- .map((role) => (role as any).code)
- .filter((code): code is string => !!code);
+ const roleCodes = this.extractRoleCodesFromUserRoles(roles);
if (roleCodes.length > 0) {
menuList = this.filterMenuByRoles(menuList, roleCodes);
}
@@ -62,6 +66,21 @@ export class MenuProcessor {
return this.filterEmptyMenus(menuList);
}
+ /**
+ * 从用户信息中的角色解析与路由 meta.roles 对齐的标识(code,以及形如 R_XXX 的 name)
+ */
+ private extractRoleCodesFromUserRoles(roles: NonNullable): string[] {
+ const codes = new Set();
+ for (const role of roles) {
+ const r = role as { code?: string; name?: string };
+ const c = r.code?.trim();
+ if (c) codes.add(c);
+ const n = r.name?.trim();
+ if (n && /^R_[A-Z0-9_]+$/i.test(n)) codes.add(n);
+ }
+ return Array.from(codes);
+ }
+
/**
* 混合模式:后端菜单 + 前端 asyncRoutes(按路由 name 去重,后端优先)
*/
diff --git a/frontend/new-web/src/router/guards/beforeEach.ts b/frontend/new-web/src/router/guards/beforeEach.ts
index ed56e6d3..0eca146a 100644
--- a/frontend/new-web/src/router/guards/beforeEach.ts
+++ b/frontend/new-web/src/router/guards/beforeEach.ts
@@ -174,7 +174,12 @@ async function handleRouteGuard(
}
// 3. 处理动态路由注册
- if (!routeRegistry?.isRegistered() && userStore.isLogin) {
+ const menuStore = useMenuStore();
+ /** 未注册动态路由,或菜单已被清空(登出延迟 reset 与再登录竞态下可能出现「已注册但 menuList 为空」) */
+ const shouldInitRoutes =
+ userStore.isLogin && (!routeRegistry?.isRegistered() || menuStore.menuList.length === 0);
+
+ if (shouldInitRoutes) {
// 防止并发请求(快速连续导航场景)
if (routeInitInProgress) {
// 正在初始化中,等待完成后重新导航
@@ -284,6 +289,21 @@ function isStaticRoute(path: string): boolean {
return checkRoute(staticRoutes, path);
}
+/**
+ * 动态路由仍标记为已注册但侧边菜单已被清空时,先卸下动态路由再拉菜单。
+ * 典型场景:`logout` 中 `resetRouterState(500)` 延迟执行,用户在 500ms 内再次登录,
+ * 守卫若仅判断 `isRegistered()` 会跳过拉菜单,侧栏空白。
+ */
+function repairDynamicRoutesIfMenuEmpty(): void {
+ if (!routeRegistry?.isRegistered()) return;
+ const ms = useMenuStore();
+ if (ms.menuList.length > 0) return;
+ routeRegistry.unregister();
+ IframeRouteManager.getInstance().clear();
+ ms.removeAllDynamicRoutes();
+ resetRouteInitState();
+}
+
/**
* 处理动态路由注册
*/
@@ -292,6 +312,8 @@ async function handleDynamicRoutes(
next: NavigationGuardNext,
router: Router
): Promise {
+ repairDynamicRoutesIfMenuEmpty();
+
// 标记初始化进行中
routeInitInProgress = true;
diff --git a/frontend/new-web/src/router/modules/article.ts b/frontend/new-web/src/router/modules/article.ts
deleted file mode 100644
index c12e9035..00000000
--- a/frontend/new-web/src/router/modules/article.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { AppRouteRecord } from "@/types/router";
-
-export const articleRoutes: AppRouteRecord = {
- path: "/article",
- name: "Article",
- component: "/index/index",
- meta: {
- title: "menus.article.title",
- icon: "ri:book-2-line",
- roles: ["R_SUPER", "R_ADMIN"],
- },
- children: [
- {
- path: "article-list",
- name: "ArticleList",
- component: "/article/list",
- meta: {
- title: "menus.article.articleList",
- icon: "ri:article-line",
- keepAlive: true,
- authList: [
- { title: "新增", authMark: "add" },
- { title: "编辑", authMark: "edit" },
- ],
- },
- },
- {
- path: "detail/:id",
- name: "ArticleDetail",
- component: "/article/detail",
- meta: {
- title: "menus.article.articleDetail",
- isHide: true,
- keepAlive: true,
- activePath: "/article/article-list",
- },
- },
- {
- path: "comment",
- name: "ArticleComment",
- component: "/article/comment",
- meta: {
- title: "menus.article.comment",
- icon: "ri:mail-line",
- keepAlive: true,
- },
- },
- {
- path: "publish",
- name: "ArticlePublish",
- component: "/article/publish",
- meta: {
- title: "menus.article.articlePublish",
- icon: "ri:telegram-2-line",
- keepAlive: true,
- authList: [{ title: "发布", authMark: "add" }],
- },
- },
- ],
-};
diff --git a/frontend/new-web/src/router/modules/dashboard.ts b/frontend/new-web/src/router/modules/dashboard.ts
deleted file mode 100644
index 1cb5b884..00000000
--- a/frontend/new-web/src/router/modules/dashboard.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { RoutesAlias } from "@/router/routesAlias";
-import type { AppRouteRecord } from "@/types/router";
-
-/** 前端仪表盘(`/dashboard`);与后端同 path/name 时 mergeAppRouteRecords 保留后端 */
-export const dashboardRoutes: AppRouteRecord = {
- name: "Dashboard",
- path: "/dashboard",
- component: RoutesAlias.Layout,
- redirect: "/dashboard/workplace",
- meta: {
- title: "menus.dashboard.title",
- icon: "ri:pie-chart-line",
- alwaysShow: true,
- },
- children: [
- {
- path: "workplace",
- name: "DashboardWorkplace",
- component: "/dashboard/workplace",
- meta: {
- title: "menus.workplace.title",
- icon: "ri:layout-grid-line",
- keepAlive: true,
- },
- },
- {
- path: "console",
- name: "DashboardConsole",
- component: "/dashboard/console",
- meta: {
- title: "menus.dashboard.console",
- icon: "ri:home-smile-2-line",
- keepAlive: false,
- fixedTab: true,
- },
- },
- {
- path: "analysis",
- name: "DashboardAnalysis",
- component: "/dashboard/analysis",
- meta: {
- title: "menus.dashboard.analysis",
- icon: "ri:align-item-bottom-line",
- keepAlive: false,
- },
- },
- {
- path: "ecommerce",
- name: "DashboardEcommerce",
- component: "/dashboard/ecommerce",
- meta: {
- title: "menus.dashboard.ecommerce",
- icon: "ri:bar-chart-box-line",
- keepAlive: false,
- },
- },
- ],
-};
diff --git a/frontend/new-web/src/router/modules/examples.ts b/frontend/new-web/src/router/modules/examples.ts
deleted file mode 100644
index 76738dd8..00000000
--- a/frontend/new-web/src/router/modules/examples.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import { AppRouteRecord } from "@/types/router";
-
-export const examplesRoutes: AppRouteRecord = {
- path: "/examples",
- name: "Examples",
- component: "/index/index",
- meta: {
- title: "menus.examples.title",
- icon: "ri:sparkling-line",
- },
- children: [
- {
- path: "permission",
- name: "Permission",
- component: "",
- meta: {
- title: "menus.examples.permission.title",
- icon: "ri:fingerprint-line",
- },
- children: [
- {
- path: "switch-role",
- name: "PermissionSwitchRole",
- component: "/examples/permission/switch-role",
- meta: {
- title: "menus.examples.permission.switchRole",
- icon: "ri:contacts-line",
- keepAlive: true,
- },
- },
- {
- path: "button-auth",
- name: "PermissionButtonAuth",
- component: "/examples/permission/button-auth",
- meta: {
- title: "menus.examples.permission.buttonAuth",
- icon: "ri:mouse-line",
- keepAlive: true,
- authList: [
- { title: "新增", authMark: "add" },
- { title: "编辑", authMark: "edit" },
- { title: "删除", authMark: "delete" },
- { title: "导出", authMark: "export" },
- { title: "查看", authMark: "view" },
- { title: "发布", authMark: "publish" },
- { title: "配置", authMark: "config" },
- { title: "管理", authMark: "manage" },
- ],
- },
- },
- {
- path: "page-visibility",
- name: "PermissionPageVisibility",
- component: "/examples/permission/page-visibility",
- meta: {
- title: "menus.examples.permission.pageVisibility",
- icon: "ri:user-3-line",
- keepAlive: true,
- roles: ["R_SUPER"],
- },
- },
- ],
- },
- {
- path: "tabs",
- name: "Tabs",
- component: "/examples/tabs",
- meta: {
- title: "menus.examples.tabs",
- icon: "ri:price-tag-line",
- },
- },
- {
- path: "tables/basic",
- name: "TablesBasic",
- component: "/examples/tables/basic",
- meta: {
- title: "menus.examples.tablesBasic",
- icon: "ri:layout-grid-line",
- keepAlive: true,
- },
- },
- {
- path: "tables",
- name: "Tables",
- component: "/examples/tables",
- meta: {
- title: "menus.examples.tables",
- icon: "ri:table-3",
- keepAlive: true,
- },
- },
- {
- path: "forms",
- name: "Forms",
- component: "/examples/forms",
- meta: {
- title: "menus.examples.forms",
- icon: "ri:table-view",
- keepAlive: true,
- },
- },
- {
- path: "form/search-bar",
- name: "SearchBar",
- component: "/examples/forms/search-bar",
- meta: {
- title: "menus.examples.searchBar",
- icon: "ri:table-line",
- keepAlive: true,
- },
- },
- {
- path: "tables/tree",
- name: "TablesTree",
- component: "/examples/tables/tree",
- meta: {
- title: "menus.examples.tablesTree",
- icon: "ri:layout-2-line",
- keepAlive: true,
- },
- },
- {
- path: "socket-chat",
- name: "SocketChat",
- component: "/examples/socket-chat",
- meta: {
- title: "menus.examples.socketChat",
- icon: "ri:shake-hands-line",
- keepAlive: true,
- },
- },
- ],
-};
diff --git a/frontend/new-web/src/router/modules/index.ts b/frontend/new-web/src/router/modules/index.ts
index b598549f..b8509787 100644
--- a/frontend/new-web/src/router/modules/index.ts
+++ b/frontend/new-web/src/router/modules/index.ts
@@ -1,23 +1,9 @@
import { AppRouteRecord } from "@/types/router";
-import { dashboardRoutes } from "./dashboard";
-import { templateRoutes } from "./template";
import { widgetsRoutes } from "./widgets";
-import { examplesRoutes } from "./examples";
import { systemRoutes } from "./system";
-import { articleRoutes } from "./article";
-import { resultRoutes } from "./result";
import { exceptionRoutes } from "./exception";
/**
* 导出所有模块化路由
*/
-export const routeModules: AppRouteRecord[] = [
- dashboardRoutes,
- templateRoutes,
- widgetsRoutes,
- examplesRoutes,
- systemRoutes,
- articleRoutes,
- resultRoutes,
- exceptionRoutes,
-];
+export const routeModules: AppRouteRecord[] = [widgetsRoutes, systemRoutes, exceptionRoutes];
diff --git a/frontend/new-web/src/router/modules/result.ts b/frontend/new-web/src/router/modules/result.ts
deleted file mode 100644
index 98b6c8a5..00000000
--- a/frontend/new-web/src/router/modules/result.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { AppRouteRecord } from "@/types/router";
-
-export const resultRoutes: AppRouteRecord = {
- path: "/result",
- name: "Result",
- component: "/index/index",
- meta: {
- title: "menus.result.title",
- icon: "ri:checkbox-circle-line",
- },
- children: [
- {
- path: "success",
- name: "ResultSuccess",
- component: "/result/success",
- meta: {
- title: "menus.result.success",
- icon: "ri:checkbox-circle-line",
- keepAlive: true,
- },
- },
- {
- path: "fail",
- name: "ResultFail",
- component: "/result/fail",
- meta: {
- title: "menus.result.fail",
- icon: "ri:close-circle-line",
- keepAlive: true,
- },
- },
- ],
-};
diff --git a/frontend/new-web/src/router/modules/system.ts b/frontend/new-web/src/router/modules/system.ts
index 0999a867..ca7c2422 100644
--- a/frontend/new-web/src/router/modules/system.ts
+++ b/frontend/new-web/src/router/modules/system.ts
@@ -1,58 +1,43 @@
import { AppRouteRecord } from "@/types/router";
+/** Art 模板自带演示路由:与后端「系统管理」路径隔离,避免合并时被覆盖或冲突 */
export const systemRoutes: AppRouteRecord = {
- path: "/system",
- name: "System",
+ path: "/art-system",
+ name: "ArtSystem",
component: "/index/index",
meta: {
title: "menus.system.title",
icon: "ri:user-3-line",
- roles: ["R_SUPER", "R_ADMIN"],
},
children: [
{
path: "user",
- name: "User",
+ name: "ArtTplUser",
component: "/system/user",
meta: {
title: "menus.system.user",
icon: "ri:user-line",
keepAlive: true,
- roles: ["R_SUPER", "R_ADMIN"],
},
},
{
path: "role",
- name: "Role",
+ name: "ArtTplRole",
component: "/system/role",
meta: {
title: "menus.system.role",
icon: "ri:user-settings-line",
keepAlive: true,
- roles: ["R_SUPER"],
- },
- },
- {
- path: "user-center",
- name: "UserCenter",
- component: "/system/user-center",
- meta: {
- title: "menus.system.userCenter",
- icon: "ri:user-line",
- isHide: true,
- keepAlive: true,
- isHideTab: true,
},
},
{
path: "menu",
- name: "Menus",
+ name: "ArtTplMenus",
component: "/system/menu",
meta: {
title: "menus.system.menu",
icon: "ri:menu-line",
keepAlive: true,
- roles: ["R_SUPER"],
authList: [
{ title: "新增", authMark: "add" },
{ title: "编辑", authMark: "edit" },
@@ -60,90 +45,5 @@ export const systemRoutes: AppRouteRecord = {
],
},
},
- {
- path: "nested",
- name: "Nested",
- component: "",
- meta: {
- title: "menus.system.nested",
- icon: "ri:menu-unfold-3-line",
- keepAlive: true,
- },
- children: [
- {
- path: "menu1",
- name: "NestedMenu1",
- component: "/system/nested/menu1",
- meta: {
- title: "menus.system.menu1",
- icon: "ri:align-justify",
- keepAlive: true,
- },
- },
- {
- path: "menu2",
- name: "NestedMenu2",
- component: "",
- meta: {
- title: "menus.system.menu2",
- icon: "ri:align-justify",
- keepAlive: true,
- },
- children: [
- {
- path: "menu2-1",
- name: "NestedMenu2-1",
- component: "/system/nested/menu2",
- meta: {
- title: "menus.system.menu21",
- icon: "ri:align-justify",
- keepAlive: true,
- },
- },
- ],
- },
- {
- path: "menu3",
- name: "NestedMenu3",
- component: "",
- meta: {
- title: "menus.system.menu3",
- icon: "ri:align-justify",
- keepAlive: true,
- },
- children: [
- {
- path: "menu3-1",
- name: "NestedMenu3-1",
- component: "/system/nested/menu3",
- meta: {
- title: "menus.system.menu31",
- keepAlive: true,
- },
- },
- {
- path: "menu3-2",
- name: "NestedMenu3-2",
- component: "",
- meta: {
- title: "menus.system.menu32",
- keepAlive: true,
- },
- children: [
- {
- path: "menu3-2-1",
- name: "NestedMenu3-2-1",
- component: "/system/nested/menu3/menu3-2",
- meta: {
- title: "menus.system.menu321",
- keepAlive: true,
- },
- },
- ],
- },
- ],
- },
- ],
- },
],
};
diff --git a/frontend/new-web/src/router/modules/template.ts b/frontend/new-web/src/router/modules/template.ts
deleted file mode 100644
index 91db8b2a..00000000
--- a/frontend/new-web/src/router/modules/template.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import { AppRouteRecord } from "@/types/router";
-
-export const templateRoutes: AppRouteRecord = {
- path: "/template",
- name: "Template",
- component: "/index/index",
- meta: {
- title: "menus.template.title",
- icon: "ri:apps-2-line",
- },
- children: [
- {
- path: "cards",
- name: "Cards",
- component: "/template/cards",
- meta: {
- title: "menus.template.cards",
- icon: "ri:wallet-line",
- keepAlive: false,
- },
- },
- {
- path: "banners",
- name: "Banners",
- component: "/template/banners",
- meta: {
- title: "menus.template.banners",
- icon: "ri:rectangle-line",
- keepAlive: false,
- },
- },
- {
- path: "charts",
- name: "Charts",
- component: "/template/charts",
- meta: {
- title: "menus.template.charts",
- icon: "ri:bar-chart-box-line",
- keepAlive: false,
- },
- },
- {
- path: "map",
- name: "Map",
- component: "/template/map",
- meta: {
- title: "menus.template.map",
- icon: "ri:map-pin-line",
- keepAlive: true,
- },
- },
- {
- path: "chat",
- name: "Chat",
- component: "/template/chat",
- meta: {
- title: "menus.template.chat",
- icon: "ri:message-3-line",
- keepAlive: true,
- },
- },
- {
- path: "calendar",
- name: "Calendar",
- component: "/template/calendar",
- meta: {
- title: "menus.template.calendar",
- icon: "ri:calendar-2-line",
- keepAlive: true,
- },
- },
- {
- path: "pricing",
- name: "Pricing",
- component: "/template/pricing",
- meta: {
- title: "menus.template.pricing",
- icon: "ri:money-cny-box-line",
- keepAlive: true,
- isFullPage: true,
- },
- },
- ],
-};
diff --git a/frontend/new-web/src/router/modules/widgets.ts b/frontend/new-web/src/router/modules/widgets.ts
index 20fc9465..87d91789 100644
--- a/frontend/new-web/src/router/modules/widgets.ts
+++ b/frontend/new-web/src/router/modules/widgets.ts
@@ -9,6 +9,36 @@ export const widgetsRoutes: AppRouteRecord = {
icon: "ri:apps-2-add-line",
},
children: [
+ {
+ path: "cards",
+ name: "Cards",
+ component: "/widgets/cards",
+ meta: {
+ title: "menus.widgets.cards",
+ icon: "ri:wallet-line",
+ keepAlive: false,
+ },
+ },
+ {
+ path: "banners",
+ name: "Banners",
+ component: "/widgets/banners",
+ meta: {
+ title: "menus.widgets.banners",
+ icon: "ri:rectangle-line",
+ keepAlive: false,
+ },
+ },
+ {
+ path: "charts",
+ name: "Charts",
+ component: "/widgets/charts",
+ meta: {
+ title: "menus.widgets.charts",
+ icon: "ri:bar-chart-box-line",
+ keepAlive: false,
+ },
+ },
{
path: "icon",
name: "Icon",
@@ -130,5 +160,255 @@ export const widgetsRoutes: AppRouteRecord = {
showTextBadge: "Hot",
},
},
+
+ /** 结果页(原 /result) */
+ {
+ path: "result",
+ name: "Result",
+ component: "",
+ meta: {
+ title: "menus.result.title",
+ icon: "ri:checkbox-circle-line",
+ },
+ children: [
+ {
+ path: "success",
+ name: "ResultSuccess",
+ component: "/widgets/result/success",
+ meta: {
+ title: "menus.result.success",
+ icon: "ri:checkbox-circle-line",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "fail",
+ name: "ResultFail",
+ component: "/widgets/result/fail",
+ meta: {
+ title: "menus.result.fail",
+ icon: "ri:close-circle-line",
+ keepAlive: true,
+ },
+ },
+ ],
+ },
+
+ /** 功能示例(原 /examples) */
+ {
+ path: "examples",
+ name: "Examples",
+ component: "",
+ meta: {
+ title: "menus.examples.title",
+ icon: "ri:sparkling-line",
+ },
+ children: [
+ {
+ path: "permission",
+ name: "Permission",
+ component: "",
+ meta: {
+ title: "menus.examples.permission.title",
+ icon: "ri:fingerprint-line",
+ },
+ children: [
+ {
+ path: "switch-role",
+ name: "PermissionSwitchRole",
+ component: "/widgets/examples/permission/switch-role",
+ meta: {
+ title: "menus.examples.permission.switchRole",
+ icon: "ri:contacts-line",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "button-auth",
+ name: "PermissionButtonAuth",
+ component: "/widgets/examples/permission/button-auth",
+ meta: {
+ title: "menus.examples.permission.buttonAuth",
+ icon: "ri:mouse-line",
+ keepAlive: true,
+ authList: [
+ { title: "新增", authMark: "add" },
+ { title: "编辑", authMark: "edit" },
+ { title: "删除", authMark: "delete" },
+ { title: "导出", authMark: "export" },
+ { title: "查看", authMark: "view" },
+ { title: "发布", authMark: "publish" },
+ { title: "配置", authMark: "config" },
+ { title: "管理", authMark: "manage" },
+ ],
+ },
+ },
+ {
+ path: "page-visibility",
+ name: "PermissionPageVisibility",
+ component: "/widgets/examples/permission/page-visibility",
+ meta: {
+ title: "menus.examples.permission.pageVisibility",
+ icon: "ri:user-3-line",
+ keepAlive: true,
+ roles: ["R_SUPER"],
+ },
+ },
+ ],
+ },
+ {
+ path: "tabs",
+ name: "Tabs",
+ component: "/widgets/examples/tabs",
+ meta: {
+ title: "menus.examples.tabs",
+ icon: "ri:price-tag-line",
+ },
+ },
+ {
+ path: "tables/basic",
+ name: "TablesBasic",
+ component: "/widgets/examples/tables/basic",
+ meta: {
+ title: "menus.examples.tablesBasic",
+ icon: "ri:layout-grid-line",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "tables",
+ name: "Tables",
+ component: "/widgets/examples/tables",
+ meta: {
+ title: "menus.examples.tables",
+ icon: "ri:table-3",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "forms",
+ name: "Forms",
+ component: "/widgets/examples/forms",
+ meta: {
+ title: "menus.examples.forms",
+ icon: "ri:table-view",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "form/search-bar",
+ name: "SearchBar",
+ component: "/widgets/examples/forms/search-bar",
+ meta: {
+ title: "menus.examples.searchBar",
+ icon: "ri:table-line",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "tables/tree",
+ name: "TablesTree",
+ component: "/widgets/examples/tables/tree",
+ meta: {
+ title: "menus.examples.tablesTree",
+ icon: "ri:layout-2-line",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "socket-chat",
+ name: "SocketChat",
+ component: "/widgets/examples/socket-chat",
+ meta: {
+ title: "menus.examples.socketChat",
+ icon: "ri:shake-hands-line",
+ keepAlive: true,
+ },
+ },
+ ],
+ },
+
+ /** 文章演示(原 /article) */
+ {
+ path: "article",
+ name: "Article",
+ component: "",
+ meta: {
+ title: "menus.article.title",
+ icon: "ri:book-2-line",
+ roles: ["R_SUPER", "R_ADMIN"],
+ },
+ children: [
+ {
+ path: "article-list",
+ name: "ArticleList",
+ component: "/widgets/article/list",
+ meta: {
+ title: "menus.article.articleList",
+ icon: "ri:article-line",
+ keepAlive: true,
+ authList: [
+ { title: "新增", authMark: "add" },
+ { title: "编辑", authMark: "edit" },
+ ],
+ },
+ },
+ {
+ path: "detail/:id",
+ name: "ArticleDetail",
+ component: "/widgets/article/detail",
+ meta: {
+ title: "menus.article.articleDetail",
+ isHide: true,
+ keepAlive: true,
+ activePath: "/widgets/article/article-list",
+ },
+ },
+ {
+ path: "comment",
+ name: "ArticleComment",
+ component: "/widgets/article/comment",
+ meta: {
+ title: "menus.article.comment",
+ icon: "ri:mail-line",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "publish",
+ name: "ArticlePublish",
+ component: "/widgets/article/publish",
+ meta: {
+ title: "menus.article.articlePublish",
+ icon: "ri:telegram-2-line",
+ keepAlive: true,
+ authList: [{ title: "发布", authMark: "add" }],
+ },
+ },
+ ],
+ },
+
+ /** 更新日志(原 views/change/log) */
+ {
+ path: "change",
+ name: "Change",
+ component: "",
+ meta: {
+ title: "menus.widgets.changeLog",
+ icon: "ri:gamepad-line",
+ },
+ children: [
+ {
+ path: "log",
+ name: "ChangeLog",
+ component: "/widgets/change/log",
+ meta: {
+ title: "menus.widgets.changeLog",
+ icon: "ri:gamepad-line",
+ keepAlive: true,
+ },
+ },
+ ],
+ },
],
};
diff --git a/frontend/new-web/src/router/routes/staticRoutes.ts b/frontend/new-web/src/router/routes/staticRoutes.ts
index e728ae3e..fa05d48e 100644
--- a/frontend/new-web/src/router/routes/staticRoutes.ts
+++ b/frontend/new-web/src/router/routes/staticRoutes.ts
@@ -64,7 +64,7 @@ export const staticRoutes: AppRouteRecordRaw[] = [
redirect: "/home",
component: Layout,
children: [
- /** 首页;壳层菜单见 mergeShellMenus */
+ /** 首页(壳层菜单见 mergeShellMenus);仪表盘同挂在下方 `dashboard` 子树 */
{
path: "home",
name: "Home",
@@ -78,9 +78,85 @@ export const staticRoutes: AppRouteRecordRaw[] = [
{
path: "profile",
name: "Profile",
- meta: { title: "个人中心", icon: "user", hidden: true },
+ meta: { title: "个人中心", icon: "ri:user-line", hidden: true },
component: () => import("@/views/current/profile.vue"),
},
+ /** 仪表盘:嵌套在根 Layout 下,与 `/home`、`/profile` 同级挂载 */
+ {
+ path: "dashboard",
+ name: "Dashboard",
+ redirect: "/dashboard/workplace",
+ component: () => import("@/views/nested/router-view-parent/index.vue"),
+ meta: {
+ title: "menus.dashboard.title",
+ icon: "ri:pie-chart-line",
+ alwaysShow: true,
+ },
+ children: [
+ {
+ path: "workplace",
+ name: "DashboardWorkplace",
+ component: () => import("@/views/dashboard/workplace/index.vue"),
+ meta: {
+ title: "menus.workplace.title",
+ icon: "ri:layout-grid-line",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "console",
+ name: "DashboardConsole",
+ component: () => import("@/views/dashboard/console/index.vue"),
+ meta: {
+ title: "menus.dashboard.console",
+ icon: "ri:home-smile-2-line",
+ keepAlive: false,
+ fixedTab: true,
+ },
+ },
+ {
+ path: "analysis",
+ name: "DashboardAnalysis",
+ component: () => import("@/views/dashboard/analysis/index.vue"),
+ meta: {
+ title: "menus.dashboard.analysis",
+ icon: "ri:align-item-bottom-line",
+ keepAlive: false,
+ },
+ },
+ {
+ path: "ecommerce",
+ name: "DashboardEcommerce",
+ component: () => import("@/views/dashboard/ecommerce/index.vue"),
+ meta: {
+ title: "menus.dashboard.ecommerce",
+ icon: "ri:bar-chart-box-line",
+ keepAlive: false,
+ },
+ },
+ {
+ path: "map",
+ name: "DashboardMap",
+ component: () => import("@/views/dashboard/map/index.vue"),
+ meta: {
+ title: "menus.dashboard.map",
+ icon: "ri:map-pin-line",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "pricing",
+ name: "DashboardPricing",
+ component: () => import("@/views/dashboard/pricing/index.vue"),
+ meta: {
+ title: "menus.dashboard.pricing",
+ icon: "ri:money-cny-box-line",
+ keepAlive: true,
+ isFullPage: true,
+ },
+ },
+ ],
+ },
],
},
{
diff --git a/frontend/new-web/src/router/utils/mergeShellMenus.ts b/frontend/new-web/src/router/utils/mergeShellMenus.ts
index 4f32d4d2..66fd4c2b 100644
--- a/frontend/new-web/src/router/utils/mergeShellMenus.ts
+++ b/frontend/new-web/src/router/utils/mergeShellMenus.ts
@@ -1,5 +1,4 @@
import type { AppRouteRecord } from "@/types/router";
-import { dashboardRoutes } from "@/router/modules/dashboard";
function normalizeMenuPath(path?: string): string {
if (!path || !path.trim()) return "";
@@ -16,6 +15,86 @@ function collectPathsAndNames(items: AppRouteRecord[], paths: Set, names
}
}
+/** 静态路由 /home 不在后端菜单树中,补侧栏入口 */
+const HOME_SHELL: AppRouteRecord = {
+ path: "/home",
+ name: "Home",
+ meta: {
+ title: "menus.home.title",
+ icon: "ri:presentation-line",
+ keepAlive: true,
+ shellRoute: true,
+ },
+};
+
+/** 与 `staticRoutes` 根 Layout 下 `/dashboard/*` 一致,仅侧栏用(无 component) */
+const dashboardMenuSource: AppRouteRecord = {
+ name: "Dashboard",
+ path: "/dashboard",
+ meta: {
+ title: "menus.dashboard.title",
+ icon: "ri:pie-chart-line",
+ alwaysShow: true,
+ },
+ children: [
+ {
+ path: "workplace",
+ name: "DashboardWorkplace",
+ meta: {
+ title: "menus.workplace.title",
+ icon: "ri:layout-grid-line",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "console",
+ name: "DashboardConsole",
+ meta: {
+ title: "menus.dashboard.console",
+ icon: "ri:home-smile-2-line",
+ keepAlive: false,
+ },
+ },
+ {
+ path: "analysis",
+ name: "DashboardAnalysis",
+ meta: {
+ title: "menus.dashboard.analysis",
+ icon: "ri:align-item-bottom-line",
+ keepAlive: false,
+ },
+ },
+ {
+ path: "ecommerce",
+ name: "DashboardEcommerce",
+ meta: {
+ title: "menus.dashboard.ecommerce",
+ icon: "ri:bar-chart-box-line",
+ keepAlive: false,
+ },
+ },
+ {
+ path: "map",
+ name: "DashboardMap",
+ meta: {
+ title: "menus.dashboard.map",
+ icon: "ri:map-pin-line",
+ keepAlive: true,
+ },
+ },
+ {
+ path: "pricing",
+ name: "DashboardPricing",
+ meta: {
+ title: "menus.dashboard.pricing",
+ icon: "ri:money-cny-box-line",
+ keepAlive: true,
+ isFullPage: true,
+ },
+ },
+ ],
+};
+
function dashboardRoutesToShellMenu(route: AppRouteRecord, parentAbs = ""): AppRouteRecord {
const raw = route.path?.trim() ?? "";
const fullPath =
@@ -36,17 +115,6 @@ function dashboardRoutesToShellMenu(route: AppRouteRecord, parentAbs = ""): AppR
};
}
-const HOME_SHELL: AppRouteRecord = {
- path: "/home",
- name: "Home",
- meta: {
- title: "menus.home.title",
- icon: "ri:presentation-line",
- keepAlive: true,
- shellRoute: true,
- },
-};
-
export function mergeShellRoutesIntoMenu(menuList: AppRouteRecord[]): AppRouteRecord[] {
const paths = new Set();
const names = new Set();
@@ -70,7 +138,7 @@ export function mergeShellRoutesIntoMenu(menuList: AppRouteRecord[]): AppRouteRe
tryPush(HOME_SHELL);
if (!paths.has("/dashboard")) {
- tryPush(dashboardRoutesToShellMenu(structuredClone(dashboardRoutes)));
+ tryPush(dashboardRoutesToShellMenu(structuredClone(dashboardMenuSource)));
}
if (additions.length === 0) return menuList;
diff --git a/frontend/new-web/src/store/modules/setting.store.ts b/frontend/new-web/src/store/modules/setting.store.ts
index ba48782f..aa0e37f3 100644
--- a/frontend/new-web/src/store/modules/setting.store.ts
+++ b/frontend/new-web/src/store/modules/setting.store.ts
@@ -36,7 +36,7 @@ import { MenuThemeType } from "@/types/store";
import AppConfig from "@/config";
import { SystemThemeEnum, MenuThemeEnum, MenuTypeEnum, ContainerWidthEnum } from "@/enums/appEnum";
import { setElementThemeColor } from "@/utils/ui";
-import { useCeremony } from "@/hooks/core/useCeremony";
+import { formatToDate } from "@/utils/common/dateUtil";
import { StorageConfig } from "@/utils/storage/storage-config";
import { SETTING_DEFAULT_CONFIG } from "@/config/setting";
import {
@@ -202,8 +202,10 @@ export const useSettingsStore = defineStore(
return customRadius.value + "rem" || SETTING_DEFAULT_CONFIG.customRadius + "rem";
});
+ /** festivalDate 存「上次完成烟花播放」的自然日 YYYY-MM-DD,与今天相同则当天不再播 */
const isShowFireworks = computed((): boolean => {
- return festivalDate.value === useCeremony().currentFestivalData.value?.date ? false : true;
+ const today = formatToDate(new Date());
+ return festivalDate.value !== today;
});
// ==============================================
diff --git a/frontend/new-web/src/store/modules/user.store.ts b/frontend/new-web/src/store/modules/user.store.ts
index a8184432..6b01d29c 100755
--- a/frontend/new-web/src/store/modules/user.store.ts
+++ b/frontend/new-web/src/store/modules/user.store.ts
@@ -314,6 +314,8 @@ export const useUserStore = defineStore(
resetAllState();
sessionStorage.removeItem("iframeRoutes");
useMenuStore().setHomePath("");
+ // 与 RouteRegistry 并行:立即清理 permissionStore 中的动态路由标记与实例,避免仅依赖延迟 reset 时侧栏/守卫状态不一致
+ usePermissionStore(store).resetRouter();
resetRouterState(500);
if (shouldNavigate) {
diff --git a/frontend/new-web/src/styles/index.scss b/frontend/new-web/src/styles/index.scss
index 9f237442..030aef4f 100644
--- a/frontend/new-web/src/styles/index.scss
+++ b/frontend/new-web/src/styles/index.scss
@@ -46,6 +46,7 @@
}
// ==================== 搜索区域样式 ====================
+
/* 与 art-card-xs 并存时边框由主题 art 卡片统一绘制,避免双层描边 */
.search-container:not(.art-card-xs) {
border: 1px solid var(--el-border-color-light);
diff --git a/frontend/new-web/src/types/config/index.ts b/frontend/new-web/src/types/config/index.ts
index d0a0b675..a40b61b6 100644
--- a/frontend/new-web/src/types/config/index.ts
+++ b/frontend/new-web/src/types/config/index.ts
@@ -71,6 +71,11 @@ export interface FestivalConfig {
isActive?: boolean;
/** 烟花播放次数(可选,默认为 3 次) */
count?: number;
+ /**
+ * 为 true 时不放烟花,进入系统后直接显示 `scrollText`(适合长期顶栏公告)
+ * 多条时间重叠时优先使用日期区间更短的那条
+ */
+ skipFireworks?: boolean;
}
// 系统基础配置
diff --git a/frontend/new-web/src/utils/constants/links.ts b/frontend/new-web/src/utils/constants/links.ts
index e60fd512..142dd5cb 100644
--- a/frontend/new-web/src/utils/constants/links.ts
+++ b/frontend/new-web/src/utils/constants/links.ts
@@ -7,32 +7,32 @@
*/
export const WEB_LINKS = {
// Github 主页
- GITHUB_HOME: "https://github.com/Daymychen/art-design-pro",
+ GITHUB_HOME: "https://github.com/fastapiadmin",
// 项目 Github 主页
- GITHUB: "https://github.com/Daymychen/art-design-pro",
+ GITHUB: "https://github.com/fastapiadmin/FastapiAdmin",
/** 备用镜像 / 国内仓库(与旧版顶栏一致时可指向业务 Gitee) */
GITEE: "https://gitee.com/fastapiadmin/FastapiAdmin",
// 个人博客
- BLOG: "https://www.artd.pro",
+ BLOG: "https://blog.csdn.net/weixin_46768253?type=blog",
// 项目文档
- DOCS: "https://www.artd.pro/docs/zh/",
+ DOCS: "https://service.fastapiadmin.com/overview/",
// 精简版本
- LiteVersion: "https://www.artd.pro/docs/zh/guide/lite-version.html",
+ LiteVersion: "https://gitee.com/fastapiadmin/FastCloud",
// v2.6.1版本
- OldVersion: "https://www.artd.pro/v2/",
+ OldVersion: "https://github.com/fastapiadmin/FastapiAdmin/tree/v2.0.0",
// 项目社区
- COMMUNITY: "https://www.artd.pro/docs/zh/community/communicate.html",
+ COMMUNITY: "https://service.fastapiadmin.com",
// 个人 Bilibili 主页
BILIBILI: "https://space.bilibili.com/425500936?spm_id_from=333.1007.0.0",
// 项目介绍
- INTRODUCE: "https://www.artd.pro/docs/zh/guide/introduce.html",
+ INTRODUCE: "https://service.fastapiadmin.com",
};
diff --git a/frontend/new-web/src/utils/sys/upgrade.ts b/frontend/new-web/src/utils/sys/upgrade.ts
index e2f73181..767b824b 100644
--- a/frontend/new-web/src/utils/sys/upgrade.ts
+++ b/frontend/new-web/src/utils/sys/upgrade.ts
@@ -241,13 +241,13 @@ class VersionManager {
// 首次访问处理
if (this.isFirstVisit(storedVersion)) {
this.setStoredVersion(StorageConfig.CURRENT_VERSION);
- // console.info('[Upgrade] 首次访问,已设置当前版本')
+ console.info("[Upgrade] 首次访问,已设置当前版本");
return;
}
// 版本相同,无需升级
if (this.isSameVersion(storedVersion!)) {
- // console.debug('[Upgrade] 版本相同,无需升级')
+ console.debug("[Upgrade] 版本相同,无需升级");
return;
}
diff --git a/frontend/new-web/src/views/current/profile.vue b/frontend/new-web/src/views/current/profile.vue
index 5d341395..2d6745d5 100644
--- a/frontend/new-web/src/views/current/profile.vue
+++ b/frontend/new-web/src/views/current/profile.vue
@@ -1,333 +1,266 @@
+
-
-
-
-
-
-
-
-
-