smart-admin-web 10.5.0 → 10.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +1 -1
  2. package/dist/api/client.js.map +1 -1
  3. package/dist/api/index.d.ts +3 -3
  4. package/dist/api/index.js +2 -2
  5. package/dist/api/index.js.map +1 -1
  6. package/dist/components/DetailPage/index.vue.js +1 -1
  7. package/dist/components/DetailPage/index2.vue.js.map +1 -1
  8. package/dist/components/IconPicker/index.vue.js.map +1 -1
  9. package/dist/components/ImportWizard/index.vue.js +1 -1
  10. package/dist/components/ImportWizard/index2.vue.js.map +1 -1
  11. package/dist/components/TableZoomButton/index.vue.js.map +1 -1
  12. package/dist/composables/useConfirm.js.map +1 -1
  13. package/dist/composables/useLayoutMenu.js.map +1 -1
  14. package/dist/composables/useRealtime.d.ts +1 -1
  15. package/dist/composables/useRealtime.js.map +1 -1
  16. package/dist/createSmartAdmin.d.ts +2 -2
  17. package/dist/createSmartAdmin.js +15 -14
  18. package/dist/createSmartAdmin.js.map +1 -1
  19. package/dist/index.js +27 -26
  20. package/dist/layouts/default.vue.js +2 -2
  21. package/dist/layouts/default2.vue.js.map +1 -1
  22. package/dist/lib/icons.js.map +1 -1
  23. package/dist/lib/markdown.js.map +1 -1
  24. package/dist/lib/runtime.js +1 -1
  25. package/dist/locales/en-US.js +1 -1
  26. package/dist/locales/en-US.js.map +1 -1
  27. package/dist/locales/zh-CN.js +2 -2
  28. package/dist/locales/zh-CN.js.map +1 -1
  29. package/dist/router/detailRoutes.js.map +1 -1
  30. package/dist/router/index.js.map +1 -1
  31. package/dist/router/routes.js +1 -1
  32. package/dist/router/routes.js.map +1 -1
  33. package/dist/stores/user.d.ts +2 -2
  34. package/dist/stores/user.js.map +1 -1
  35. package/dist/style.css +1 -1
  36. package/dist/theme/mix.d.ts +3 -3
  37. package/dist/theme/mix.js.map +1 -1
  38. package/dist/types/api.d.ts +1 -1
  39. package/dist/types/api.js.map +1 -1
  40. package/dist/utils/url.d.ts +2 -0
  41. package/dist/utils/url.js +9 -3
  42. package/dist/utils/url.js.map +1 -1
  43. package/dist/views/error/404.vue.js +2 -2
  44. package/dist/views/error/4042.vue.js.map +1 -1
  45. package/dist/views/login/skins/SplitPanel.vue.js +1 -1
  46. package/dist/views/login/skins/SplitPanel2.vue.js.map +1 -1
  47. package/dist/views/system/log/op/index.vue.js +1 -1
  48. package/dist/views/system/log/op/index2.vue.js +1 -1
  49. package/dist/views/system/log/op/index2.vue.js.map +1 -1
  50. package/dist/views/system/menu/components/ButtonManager.vue.js +1 -1
  51. package/dist/views/system/menu/components/ButtonManager2.vue.js.map +1 -1
  52. package/dist/views/system/menu/components/MenuFormModal.vue.js +3 -3
  53. package/dist/views/system/menu/components/MenuFormModal2.vue.js.map +1 -1
  54. package/dist/views/system/menu/index.vue.js +2 -2
  55. package/dist/views/system/menu/index2.vue.js +6 -6
  56. package/dist/views/system/menu/index2.vue.js.map +1 -1
  57. package/dist/views/system/module/index.vue.js +2 -2
  58. package/dist/views/system/module/index2.vue.js +2 -2
  59. package/dist/views/system/module/index2.vue.js.map +1 -1
  60. package/dist/views/system/user/components/ResetPasswordModal.vue.js +1 -1
  61. package/dist/views/system/user/components/ResetPasswordModal2.vue.js.map +1 -1
  62. package/dist/views/system/user/components/UserFormModal.vue.js +1 -1
  63. package/dist/views/system/user/components/UserFormModal2.vue.js.map +1 -1
  64. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.vue.js","sources":["../../../src/components/IconPicker/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// SmartAdmin 图标选择器:复用 npm 包的 SmartIconPicker,仅把 SmartAdmin 的 i18n 文案注入 labels(包本身不带 i18n 框架)。\n// 图标集/本地 SVG 由 createSmartAdmin 里的 setupIcons() 全局注册;这里不传 collections,复用全局注册顺序(ph 为首/默认页)。\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { SmartIconPicker } from 'smart-naive-icon'\n\nconst model = defineModel<string>({ default: '' })\nconst props = withDefaults(defineProps<{ placeholder?: string; clearable?: boolean }>(), {\n placeholder: '',\n clearable: true,\n})\n\nconst { t } = useI18n()\n\nconst labels = computed(() => ({\n placeholder: props.placeholder || t('iconPicker.placeholder'),\n title: t('iconPicker.title'),\n search: t('iconPicker.search'),\n local: t('iconPicker.local'),\n online: t('iconPicker.online'),\n onlinePlaceholder: t('iconPicker.onlinePlaceholder'),\n use: t('iconPicker.use'),\n offlineHint: t('iconPicker.offlineHint'),\n loading: t('common.loading'), // 包用 labels.loading;SmartAdmin 复用 common.loading\n empty: t('iconPicker.empty'),\n more: t('iconPicker.more', { n: '{n}' }), // 透传字面量 {n} 给包(否则 vue-i18n 会吃掉占位,计数丢失)\n}))\n</script>\n\n<template>\n <!-- 选择器 chrome 用 ph(已预热,离线优先);SmartAdmin 语言包驱动全部文案 -->\n <SmartIconPicker\n v-model=\"model\"\n :labels=\"labels\"\n :clearable=\"clearable\"\n search-icon=\"ph:magnifying-glass\"\n clear-icon=\"ph:x\"\n />\n</template>\n"],"names":["model","_useModel","__props","props","t","useI18n","labels","computed","_createBlock","_unref","SmartIconPicker","$event"],"mappings":";;;;;;;;;;;;;;AAOA,UAAMA,IAAQC,EAAmBC,GAAA,YAAgB,GAC3CC,IAAQD,GAKR,EAAE,GAAAE,EAAA,IAAMC,EAAA,GAERC,IAASC,EAAS,OAAO;AAAA,MAC7B,aAAaJ,EAAM,eAAeC,EAAE,wBAAwB;AAAA,MAC5D,OAAOA,EAAE,kBAAkB;AAAA,MAC3B,QAAQA,EAAE,mBAAmB;AAAA,MAC7B,OAAOA,EAAE,kBAAkB;AAAA,MAC3B,QAAQA,EAAE,mBAAmB;AAAA,MAC7B,mBAAmBA,EAAE,8BAA8B;AAAA,MACnD,KAAKA,EAAE,gBAAgB;AAAA,MACvB,aAAaA,EAAE,wBAAwB;AAAA,MACvC,SAASA,EAAE,gBAAgB;AAAA;AAAA,MAC3B,OAAOA,EAAE,kBAAkB;AAAA,MAC3B,MAAMA,EAAE,mBAAmB,EAAE,GAAG,OAAO;AAAA;AAAA,IAAA,EACvC;2BAKAI,EAMEC,EAAAC,CAAA,GAAA;AAAA,kBALSV,EAAA;AAAA,oDAAAA,EAAK,QAAAW;AAAA,MACb,QAAQL,EAAA;AAAA,MACR,WAAWJ,EAAA;AAAA,MACZ,eAAY;AAAA,MACZ,cAAW;AAAA,IAAA;;;"}
1
+ {"version":3,"file":"index.vue.js","sources":["../../../src/components/IconPicker/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// SmartAdmin 图标选择器:复用 npm 包的 SmartIconPicker,仅把 SmartAdmin 的 i18n 文案注入 labels(包本身不带 i18n 框架)。\n// 图标集/本地 SVG 由 createSmartAdmin 里的 setupIcons() 全局注册;这里不传 collections,复用全局注册顺序(ph 为首/默认页)。\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { SmartIconPicker } from 'smart-naive-icon'\n\nconst model = defineModel<string>({ default: '' })\nconst props = withDefaults(defineProps<{ placeholder?: string; clearable?: boolean }>(), {\n placeholder: '',\n clearable: true,\n})\n\nconst { t } = useI18n()\n\nconst labels = computed(() => ({\n placeholder: props.placeholder || t('iconPicker.placeholder'),\n title: t('iconPicker.title'),\n search: t('iconPicker.search'),\n local: t('iconPicker.local'),\n online: t('iconPicker.online'),\n onlinePlaceholder: t('iconPicker.onlinePlaceholder'),\n use: t('iconPicker.use'),\n offlineHint: t('iconPicker.offlineHint'),\n loading: t('common.loading'), // 包用 labels.loading;SmartAdmin 复用 common.loading\n empty: t('iconPicker.empty'),\n more: t('iconPicker.more', { n: '{n}' }), // 透传字面量 {n} 给包(否则 vue-i18n 会吃掉占位,计数丢失)\n}))\n</script>\n\n<template>\n <!-- 选择器 chrome 用 ph(这两个图标在启动时同步注册的离线子集里);SmartAdmin 语言包驱动全部文案 -->\n <SmartIconPicker\n v-model=\"model\"\n :labels=\"labels\"\n :clearable=\"clearable\"\n search-icon=\"ph:magnifying-glass\"\n clear-icon=\"ph:x\"\n />\n</template>\n"],"names":["model","_useModel","__props","props","t","useI18n","labels","computed","_createBlock","_unref","SmartIconPicker","$event"],"mappings":";;;;;;;;;;;;;;AAOA,UAAMA,IAAQC,EAAmBC,GAAA,YAAgB,GAC3CC,IAAQD,GAKR,EAAE,GAAAE,EAAA,IAAMC,EAAA,GAERC,IAASC,EAAS,OAAO;AAAA,MAC7B,aAAaJ,EAAM,eAAeC,EAAE,wBAAwB;AAAA,MAC5D,OAAOA,EAAE,kBAAkB;AAAA,MAC3B,QAAQA,EAAE,mBAAmB;AAAA,MAC7B,OAAOA,EAAE,kBAAkB;AAAA,MAC3B,QAAQA,EAAE,mBAAmB;AAAA,MAC7B,mBAAmBA,EAAE,8BAA8B;AAAA,MACnD,KAAKA,EAAE,gBAAgB;AAAA,MACvB,aAAaA,EAAE,wBAAwB;AAAA,MACvC,SAASA,EAAE,gBAAgB;AAAA;AAAA,MAC3B,OAAOA,EAAE,kBAAkB;AAAA,MAC3B,MAAMA,EAAE,mBAAmB,EAAE,GAAG,OAAO;AAAA;AAAA,IAAA,EACvC;2BAKAI,EAMEC,EAAAC,CAAA,GAAA;AAAA,kBALSV,EAAA;AAAA,oDAAAA,EAAK,QAAAW;AAAA,MACb,QAAQL,EAAA;AAAA,MACR,WAAWJ,EAAA;AAAA,MACZ,eAAY;AAAA,MACZ,cAAW;AAAA,IAAA;;;"}
@@ -1,7 +1,7 @@
1
1
  import o from "./index2.vue.js";
2
2
  /* empty css */
3
3
  import r from "../../_virtual/_plugin-vue_export-helper.js";
4
- const a = /* @__PURE__ */ r(o, [["__scopeId", "data-v-72e37069"]]);
4
+ const a = /* @__PURE__ */ r(o, [["__scopeId", "data-v-20fd3250"]]);
5
5
  export {
6
6
  a as default
7
7
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index2.vue.js","sources":["../../../src/components/ImportWizard/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 四步导入向导:①上传 ②列映射 ③预览改错(裸 n-data-table)④结果。\n// API 由父级注入(用户导入走 userApi 六方法),组件对资源无感知,可复用到其他档案。\nimport { computed, h, ref, shallowRef, watch } from 'vue'\nimport {\n NModal,\n NSteps,\n NStep,\n NUpload,\n NUploadDragger,\n NText,\n NButton,\n NSpace,\n NSelect,\n NDataTable,\n NInput,\n NSwitch,\n NRadioGroup,\n NRadio,\n NAlert,\n NTooltip,\n NTag,\n useMessage,\n type DataTableColumns,\n type UploadFileInfo,\n} from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport AppIcon from '#/components/AppIcon.vue'\nimport DictSelect from '#/components/DictSelect/index.vue'\nimport { translateError } from '#/utils/error'\nimport { triggerBlobDownload } from '#/utils/download'\nimport { hardErrorsOf as hardErrors, isDuplicateOnly, isHardError } from '#/utils/importDup'\nimport type {\n CellError,\n DuplicateStrategy as DupStrategyNum,\n ImportColumn,\n ImportCommitResult,\n ImportPreview,\n ImportRow,\n} from '#/types/api'\nimport { DuplicateStrategy } from '#/types/api'\n\nexport interface ImportWizardApi {\n downloadTemplate: () => Promise<Blob>\n preview: (file: File, mapping?: Record<string, string>) => Promise<ImportPreview>\n validate: (rows: ImportRow[]) => Promise<ImportPreview>\n commit: (rows: ImportRow[], strategy: DupStrategyNum) => Promise<ImportCommitResult>\n errorReport: (rows: ImportRow[]) => Promise<Blob>\n}\n\nconst show = defineModel<boolean>('show', { default: false })\n\nconst props = defineProps<{\n api: ImportWizardApi\n /**\n * 弹窗标题。缺省是 `import.wizardTitle`(「导入用户」)——那是内核用户导入的文案,\n * 本组件被别的档案复用时必须传自己的标题,否则会顶着「导入用户」导别的东西。\n */\n title?: string\n /** 模板下载默认文件名 */\n templateFileName?: string\n /** 错误报告默认文件名 */\n errorReportFileName?: string\n /**\n * 可选的重复策略,按给定顺序渲染;缺省三种都给。\n * 用于档案本身不支持某种策略的场景 —— 例如条码储位只允许「跳过已存在」,\n * 覆盖会把条码挪到别的库位,不是导入该干的事。\n */\n strategies?: DupStrategyNum[]\n}>()\n\nconst emit = defineEmits<{\n /** 提交成功(含部分成功)后触发,父级通常 refresh 列表 */\n done: []\n}>()\n\nconst { t } = useI18n()\nconst message = useMessage()\n\n// ── 状态 ──\nconst step = ref(1) // n-steps 1-based\nconst loading = ref(false)\nconst file = ref<File | null>(null)\nconst fileList = ref<UploadFileInfo[]>([])\nconst headers = shallowRef<string[]>([])\n/** 表头 → 列 Key(空串 = 不映射) */\nconst mapping = ref<Record<string, string>>({})\nconst columns = shallowRef<ImportColumn[]>([])\n// rows 刻意保持深响应:setCell 是就地改 row.cells / row.errors(可编辑预览表),\n// 换成 shallowRef 后单元格改完不会重渲,红底也不会消 —— 要改得先把编辑改成不可变替换。\nconst rows = ref<ImportRow[]>([])\nconst columnErrors = ref<ImportPreview['columnErrors']>([])\nconst onlyErrors = ref(false)\nconst strategy = ref<DupStrategyNum>(DuplicateStrategy.Skip)\nconst commitResult = ref<ImportCommitResult | null>(null)\n\n/** 策略标签用函数形式,切换语言即时生效(与 SmartTable 列 title 同理)。 */\nconst strategyLabels: Record<DupStrategyNum, () => string> = {\n [DuplicateStrategy.Skip]: () => t('import.strategySkip'),\n [DuplicateStrategy.Overwrite]: () => t('import.strategyOverwrite'),\n [DuplicateStrategy.Error]: () => t('import.strategyError'),\n}\n/** 未指定 strategies 时三种都给,保持既有页面的行为不变。 */\nconst strategyOptions = computed(() =>\n (\n props.strategies ?? [\n DuplicateStrategy.Skip,\n DuplicateStrategy.Overwrite,\n DuplicateStrategy.Error,\n ]\n ).map(value => ({ value, label: strategyLabels[value] })),\n)\n\n// 「已存在」按策略呈现的判定见 utils/importDup(那里也记着与后端的镜像关系)\nconst isHard = (e: CellError) => isHardError(e, strategy.value)\nconst hardErrorsOf = (r: ImportRow) => hardErrors(r, strategy.value)\n/** 需要用户动手改的行数(不含「已存在」——那些按策略自动处理)。 */\nconst errorRows = computed(() => rows.value.filter(r => hardErrorsOf(r).length > 0).length)\n/** 只是「已存在」、会被策略正常处理的行数。 */\nconst duplicateRows = computed(\n () => rows.value.filter(r => isDuplicateOnly(r, strategy.value)).length,\n)\n\nfunction reset() {\n step.value = 1\n loading.value = false\n file.value = null\n fileList.value = []\n headers.value = []\n mapping.value = {}\n columns.value = []\n rows.value = []\n columnErrors.value = []\n onlyErrors.value = false\n // 默认选 Skip;调用方限制了可选策略且不含 Skip 时退到第一个可选项,避免选中一个没渲染的值。\n strategy.value = strategyOptions.value.some(o => o.value === DuplicateStrategy.Skip)\n ? DuplicateStrategy.Skip\n : (strategyOptions.value[0]?.value ?? DuplicateStrategy.Skip)\n commitResult.value = null\n}\n\nwatch(show, v => {\n if (v) reset()\n})\n\nfunction applyPreview(p: ImportPreview) {\n headers.value = [...p.headers]\n mapping.value = { ...p.mapping }\n columns.value = p.columns\n rows.value = p.rows.map(r => ({\n index: r.index,\n cells: { ...r.cells },\n errors: [...r.errors],\n }))\n columnErrors.value = p.columnErrors\n}\n\nasync function onDownloadTemplate() {\n loading.value = true\n try {\n const blob = await props.api.downloadTemplate()\n triggerBlobDownload(blob, props.templateFileName ?? 'import-template.xlsx')\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\nfunction onFileChange(list: UploadFileInfo[]) {\n // 只保留最后选中的一个 xlsx\n fileList.value = list.slice(-1)\n const f = list[list.length - 1]?.file\n file.value = f instanceof File ? f : null\n}\n\nfunction onFileRemove() {\n fileList.value = []\n file.value = null\n}\n\n/** ①→②:带文件预览(mapping 空 = 服务端自动匹配) */\nasync function goMapping() {\n if (!file.value) {\n message.warning(t('import.needFile'))\n return\n }\n loading.value = true\n try {\n const p = await props.api.preview(file.value)\n applyPreview(p)\n // 确保每个表头在 mapping 里有键(未匹配的给空串,便于 n-select 显示「不映射」)\n for (const header of p.headers) {\n if (!(header in mapping.value)) mapping.value[header] = ''\n }\n step.value = 2\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\n/** ②→③:按用户调整后的映射重新预览 */\nasync function goPreview() {\n if (!file.value) return\n // 去掉「不映射」空串,只传有效映射\n const map: Record<string, string> = {}\n for (const [header, key] of Object.entries(mapping.value)) {\n if (key) map[header] = key\n }\n loading.value = true\n try {\n const p = await props.api.preview(file.value, map)\n applyPreview(p)\n step.value = 3\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\nconst targetColumnOptions = computed(() => [\n { label: t('import.unmap'), value: '' },\n ...columns.value.map(c => ({\n label: c.required ? `${c.title} *` : c.title,\n value: c.key,\n })),\n])\n\nfunction cellError(row: ImportRow, key: string) {\n return row.errors.find(e => e.columnKey === key)\n}\n\nfunction setCell(row: ImportRow, key: string, value: string | null) {\n row.cells[key] = value\n // 就地改后清该列旧错误,等重验;否则红底会误导\n row.errors = row.errors.filter(e => e.columnKey !== key)\n}\n\nconst displayRows = computed(() =>\n onlyErrors.value ? rows.value.filter(r => hardErrorsOf(r).length > 0) : rows.value,\n)\n\nconst previewColumns = computed<DataTableColumns<ImportRow>>(() => {\n const cols: DataTableColumns<ImportRow> = [\n {\n title: '#',\n key: '_index',\n width: 56,\n fixed: 'left',\n render: r => r.index,\n },\n ]\n for (const col of columns.value) {\n cols.push({\n title: () => (col.required ? `${col.title} *` : col.title),\n key: col.key,\n minWidth: 120,\n ellipsis: { tooltip: false },\n render: row => {\n const err = cellError(row, col.key)\n // 「已存在」在跳过/覆盖策略下不算错:走警示底色,输入框也不置 error 态\n const soft = err !== undefined && !isHard(err)\n const val = row.cells[col.key] ?? null\n const editor = col.dictTypeCode\n ? h(DictSelect, {\n typeCode: col.dictTypeCode,\n value: val,\n clearable: !col.required,\n size: 'small',\n // 弹层不跟随触发器宽度:向导的列很窄(minWidth 120,实测触发器约 66px),\n // 跟随会把「启用」「停用」截成「启..」「停..」。普通表单里下拉够宽,\n // 跟随反而更整齐 —— 所以只在向导关掉,不动共用的 DictSelect。\n consistentMenuWidth: false,\n style: { width: '100%' },\n status: err && !soft ? 'error' : undefined,\n 'onUpdate:value': (v: string | null) => setCell(row, col.key, v),\n })\n : h(NInput, {\n value: val ?? '',\n size: 'small',\n status: err && !soft ? 'error' : undefined,\n onUpdateValue: (v: string) => setCell(row, col.key, v),\n })\n const cell = h(\n 'div',\n {\n class: !err\n ? 'import-cell'\n : soft\n ? 'import-cell import-cell--dup'\n : 'import-cell import-cell--error',\n },\n [editor],\n )\n if (!err) return cell\n return h(\n NTooltip,\n { trigger: 'hover' },\n {\n trigger: () => cell,\n default: () =>\n soft ? t(`import.dupHint.${strategy.value}`) : translateError(err.code),\n },\n )\n },\n })\n }\n cols.push({\n title: () => t('import.errors'),\n key: '_errors',\n width: 160,\n fixed: 'right',\n render: r => {\n if (r.errors.length === 0)\n return h(NTag, { size: 'small', type: 'success', bordered: false }, () => t('import.ok'))\n const hard = hardErrorsOf(r)\n // 只剩「已存在」:不是要用户改的错,给警示标签 +「将按策略处理」而非红色错误数\n if (hard.length === 0) {\n return h(\n NTooltip,\n { trigger: 'hover' },\n {\n trigger: () =>\n h(NTag, { size: 'small', type: 'warning', bordered: false }, () =>\n t('import.dupTag'),\n ),\n default: () => t(`import.dupHint.${strategy.value}`),\n },\n )\n }\n return h(\n NTooltip,\n { trigger: 'hover' },\n {\n trigger: () =>\n h(NTag, { size: 'small', type: 'error', bordered: false }, () =>\n t('import.errorCount', { n: hard.length }),\n ),\n default: () => hard.map(e => `${e.columnKey}: ${translateError(e.code)}`).join('\\n'),\n },\n )\n },\n })\n return cols\n})\n\nasync function revalidate() {\n // 变异判据:请求必须带 rows;空数组也是明确失败路径(服务端会返回 0 行)\n loading.value = true\n try {\n const p = await props.api.validate(rows.value)\n // 保留当前列定义(重验不回传 headers/mapping;columns 仍有)\n rows.value = p.rows.map(r => ({\n index: r.index,\n cells: { ...r.cells },\n errors: [...r.errors],\n }))\n columns.value = p.columns.length ? p.columns : columns.value\n // 用本地按策略算出的硬错误数,不用 p.errorRows —— 后端不知道当前策略,会把「已存在」也计进去\n message.success(t('import.revalidated', { errors: errorRows.value, total: p.total }))\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\nasync function doCommit() {\n loading.value = true\n try {\n const result = await props.api.commit(rows.value, strategy.value)\n commitResult.value = result\n step.value = 4\n if (result.inserted + result.updated > 0) emit('done')\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\n/** ④ 失败行接回 ③ 继续改 */\nfunction backToEditFailures() {\n if (!commitResult.value) return\n rows.value = commitResult.value.failures.map(r => ({\n index: r.index,\n cells: { ...r.cells },\n errors: [...r.errors],\n }))\n onlyErrors.value = true\n commitResult.value = null\n step.value = 3\n}\n\nasync function downloadErrorReport() {\n const source =\n step.value === 4 && commitResult.value\n ? commitResult.value.failures\n : rows.value.filter(r => r.errors.length > 0)\n if (source.length === 0) {\n message.warning(t('import.noErrorRows'))\n return\n }\n loading.value = true\n try {\n const blob = await props.api.errorReport(source)\n triggerBlobDownload(blob, props.errorReportFileName ?? 'import-errors.xlsx')\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\nfunction close() {\n show.value = false\n}\n</script>\n\n<template>\n <n-modal\n v-model:show=\"show\"\n preset=\"card\"\n :title=\"props.title ?? t('import.wizardTitle')\"\n :style=\"{ width: 'min(1100px, 96vw)' }\"\n :mask-closable=\"false\"\n :closable=\"!loading\"\n :close-on-esc=\"!loading\"\n >\n <n-steps :current=\"step\" size=\"small\" class=\"import-steps\">\n <n-step :title=\"t('import.stepUpload')\" />\n <n-step :title=\"t('import.stepMapping')\" />\n <n-step :title=\"t('import.stepPreview')\" />\n <n-step :title=\"t('import.stepResult')\" />\n </n-steps>\n\n <!-- ① 上传 -->\n <div v-show=\"step === 1\" class=\"import-pane\">\n <n-space vertical :size=\"12\">\n <n-button secondary :loading=\"loading\" @click=\"onDownloadTemplate\">\n <template #icon><AppIcon icon=\"ph:download-simple\" :size=\"16\" /></template>\n {{ t('import.downloadTemplate') }}\n </n-button>\n <n-upload\n :file-list=\"fileList\"\n :max=\"1\"\n accept=\".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\"\n :default-upload=\"false\"\n @update:file-list=\"onFileChange\"\n @remove=\"onFileRemove\"\n >\n <n-upload-dragger>\n <div class=\"import-upload-icon\">\n <AppIcon icon=\"ph:file-xls\" :size=\"40\" />\n </div>\n <n-text style=\"font-size: 16px\">{{ t('import.dropHint') }}</n-text>\n <n-text depth=\"3\" style=\"display: block; margin-top: 8px\">\n {{ t('import.dropSub') }}\n </n-text>\n </n-upload-dragger>\n </n-upload>\n </n-space>\n </div>\n\n <!-- ② 列映射 -->\n <div v-show=\"step === 2\" class=\"import-pane\">\n <n-alert v-if=\"columnErrors.length\" type=\"warning\" style=\"margin-bottom: 12px\">\n {{ columnErrors.map(e => `${e.columnKey}: ${translateError(e.code)}`).join('; ') }}\n </n-alert>\n <n-text depth=\"3\" style=\"display: block; margin-bottom: 12px\">\n {{ t('import.mappingHint') }}\n </n-text>\n <div class=\"import-mapping\">\n <div class=\"import-mapping-head\">\n <span>{{ t('import.fileHeader') }}</span>\n <span>{{ t('import.targetColumn') }}</span>\n </div>\n <div v-for=\"hdr in headers\" :key=\"hdr\" class=\"import-mapping-row\">\n <span class=\"import-mapping-header\" :title=\"hdr\">{{ hdr }}</span>\n <n-select\n v-model:value=\"mapping[hdr]\"\n :options=\"targetColumnOptions\"\n :consistent-menu-width=\"false\"\n size=\"small\"\n />\n </div>\n </div>\n </div>\n\n <!-- ③ 预览改错(裸 n-data-table,不用 SmartTable) -->\n <div v-show=\"step === 3\" class=\"import-pane\">\n <n-space justify=\"space-between\" align=\"center\" style=\"margin-bottom: 12px\" wrap>\n <n-space align=\"center\">\n <n-text>\n {{ t('import.summary', { total: rows.length, errors: errorRows }) }}\n <template v-if=\"duplicateRows\">\n {{ t('import.summaryDup', { n: duplicateRows }) }}\n </template>\n </n-text>\n <n-switch v-model:value=\"onlyErrors\" size=\"small\">\n <template #checked>{{ t('import.onlyErrors') }}</template>\n <template #unchecked>{{ t('import.onlyErrors') }}</template>\n </n-switch>\n </n-space>\n <n-space>\n <n-button size=\"small\" :loading=\"loading\" @click=\"revalidate\">\n <template #icon><AppIcon icon=\"ph:arrows-clockwise\" :size=\"14\" /></template>\n {{ t('import.revalidate') }}\n </n-button>\n </n-space>\n </n-space>\n <n-data-table\n size=\"small\"\n :columns=\"previewColumns\"\n :data=\"displayRows\"\n :row-key=\"(r: ImportRow) => r.index\"\n :max-height=\"420\"\n :scroll-x=\"Math.max(800, columns.length * 140 + 220)\"\n />\n <div class=\"import-strategy\">\n <n-text strong style=\"margin-right: 12px\">{{ t('import.strategy') }}</n-text>\n <n-radio-group v-model:value=\"strategy\" name=\"dup-strategy\">\n <n-space>\n <n-radio v-for=\"option in strategyOptions\" :key=\"option.value\" :value=\"option.value\">\n {{ option.label() }}\n </n-radio>\n </n-space>\n </n-radio-group>\n </div>\n </div>\n\n <!-- ④ 结果 -->\n <div v-show=\"step === 4\" class=\"import-pane\">\n <n-alert\n v-if=\"commitResult\"\n :type=\"commitResult.failed > 0 ? 'warning' : 'success'\"\n :title=\"t('import.resultTitle')\"\n >\n <div>{{ t('import.resultTotal', { n: commitResult.total }) }}</div>\n <div>{{ t('import.resultInserted', { n: commitResult.inserted }) }}</div>\n <div>{{ t('import.resultUpdated', { n: commitResult.updated }) }}</div>\n <div>{{ t('import.resultSkipped', { n: commitResult.skipped }) }}</div>\n <div>{{ t('import.resultFailed', { n: commitResult.failed }) }}</div>\n </n-alert>\n <n-space style=\"margin-top: 16px\">\n <n-button\n v-if=\"commitResult && commitResult.failed > 0\"\n type=\"warning\"\n @click=\"backToEditFailures\"\n >\n {{ t('import.editFailures') }}\n </n-button>\n <n-button\n v-if=\"commitResult && commitResult.failed > 0\"\n secondary\n :loading=\"loading\"\n @click=\"downloadErrorReport\"\n >\n <template #icon><AppIcon icon=\"ph:download-simple\" :size=\"16\" /></template>\n {{ t('import.downloadErrorReport') }}\n </n-button>\n </n-space>\n </div>\n\n <template #footer>\n <n-space justify=\"end\">\n <n-button :disabled=\"loading\" @click=\"close\">\n {{ step === 4 ? t('common.close') : t('common.cancel') }}\n </n-button>\n <n-button v-if=\"step === 3\" secondary :disabled=\"loading\" @click=\"downloadErrorReport\">\n {{ t('import.downloadErrorReport') }}\n </n-button>\n <n-button v-if=\"step > 1 && step < 4\" :disabled=\"loading\" @click=\"step = step - 1\">\n {{ t('import.prev') }}\n </n-button>\n <n-button\n v-if=\"step === 1\"\n type=\"primary\"\n :loading=\"loading\"\n :disabled=\"!file\"\n @click=\"goMapping\"\n >\n {{ t('import.next') }}\n </n-button>\n <n-button v-if=\"step === 2\" type=\"primary\" :loading=\"loading\" @click=\"goPreview\">\n {{ t('import.next') }}\n </n-button>\n <n-button v-if=\"step === 3\" type=\"primary\" :loading=\"loading\" @click=\"doCommit\">\n {{ t('import.commit') }}\n </n-button>\n </n-space>\n </template>\n </n-modal>\n</template>\n\n<style scoped>\n.import-steps {\n margin-bottom: 20px;\n}\n.import-pane {\n min-height: 200px;\n}\n.import-upload-icon {\n margin-bottom: 12px;\n color: var(--n-text-color-3);\n}\n.import-mapping-head,\n.import-mapping-row {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n align-items: center;\n}\n.import-mapping-head {\n font-weight: 600;\n font-size: 13px;\n margin-bottom: 8px;\n color: var(--n-text-color-2);\n}\n.import-mapping-row {\n margin-bottom: 8px;\n}\n.import-mapping-header {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13px;\n}\n.import-strategy {\n margin-top: 16px;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n:deep(.import-cell) {\n padding: 2px;\n border-radius: 4px;\n}\n:deep(.import-cell--error) {\n /* 用仓库自己的语义令牌(亮/暗都有定义)。别写 --n-error-color:那是 naive 组件内部的变量,\n 在这层普通 div 上未定义,整条 color-mix() 会被判无效丢弃 —— 红底静默不渲染,而 tsc/lint/build 全绿。 */\n background: var(--color-danger-bg);\n}\n:deep(.import-cell--dup) {\n /* 「库里已存在」在跳过/覆盖策略下会被正常处理,用警示色而非错误红——同上,只用语义令牌 */\n background: var(--color-warning-bg);\n}\n</style>\n"],"names":["show","_useModel","__props","props","emit","__emit","t","useI18n","message","useMessage","step","ref","loading","file","fileList","headers","shallowRef","mapping","columns","rows","columnErrors","onlyErrors","strategy","DuplicateStrategy","commitResult","strategyLabels","strategyOptions","computed","value","isHard","isHardError","hardErrorsOf","r","hardErrors","errorRows","duplicateRows","isDuplicateOnly","reset","o","_a","watch","v","applyPreview","p","onDownloadTemplate","blob","triggerBlobDownload","translateError","onFileChange","list","f","onFileRemove","goMapping","header","goPreview","map","key","e","targetColumnOptions","c","cellError","row","setCell","displayRows","previewColumns","cols","col","err","soft","val","editor","h","DictSelect","NInput","cell","NTooltip","NTag","hard","revalidate","doCommit","result","backToEditFailures","downloadErrorReport","source","close","_createBlock","_unref","NModal","$event","_createVNode","NSpace","NButton","_cache","NSteps","NStep","_withDirectives","_createElementVNode","_hoisted_1","AppIcon","_createTextVNode","NUpload","NUploadDragger","_hoisted_2","NText","_hoisted_3","NAlert","_toDisplayString","_hoisted_4","_hoisted_5","_createElementBlock","_Fragment","_renderList","hdr","_hoisted_6","NSelect","_hoisted_7","NSwitch","NDataTable","_hoisted_8","NRadioGroup","option","NRadio","_hoisted_9"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkDA,UAAMA,IAAOC,GAAoBC,GAAC,MAA0B,GAEtDC,IAAQD,GAmBRE,KAAOC,IAKP,EAAE,GAAAC,EAAA,IAAMC,GAAA,GACRC,IAAUC,GAAA,GAGVC,IAAOC,EAAI,CAAC,GACZC,IAAUD,EAAI,EAAK,GACnBE,IAAOF,EAAiB,IAAI,GAC5BG,IAAWH,EAAsB,EAAE,GACnCI,IAAUC,GAAqB,EAAE,GAEjCC,IAAUN,EAA4B,EAAE,GACxCO,IAAUF,GAA2B,EAAE,GAGvCG,IAAOR,EAAiB,EAAE,GAC1BS,IAAeT,EAAmC,EAAE,GACpDU,IAAaV,EAAI,EAAK,GACtBW,IAAWX,EAAoBY,EAAkB,IAAI,GACrDC,IAAeb,EAA+B,IAAI,GAGlDc,KAAuD;AAAA,MAC3D,CAACF,EAAkB,IAAI,GAAG,MAAMjB,EAAE,qBAAqB;AAAA,MACvD,CAACiB,EAAkB,SAAS,GAAG,MAAMjB,EAAE,0BAA0B;AAAA,MACjE,CAACiB,EAAkB,KAAK,GAAG,MAAMjB,EAAE,sBAAsB;AAAA,IAAA,GAGrDoB,IAAkBC;AAAA,MAAS,OAE7BxB,EAAM,cAAc;AAAA,QAClBoB,EAAkB;AAAA,QAClBA,EAAkB;AAAA,QAClBA,EAAkB;AAAA,MAAA,GAEpB,IAAI,CAAAK,OAAU,EAAE,OAAAA,GAAO,OAAOH,GAAeG,CAAK,IAAI;AAAA,IAAA,GAIpDC,KAAS,CAAC,MAAiBC,GAAY,GAAGR,EAAS,KAAK,GACxDS,IAAe,CAACC,MAAiBC,GAAWD,GAAGV,EAAS,KAAK,GAE7DY,IAAYP,EAAS,MAAMR,EAAK,MAAM,OAAO,CAAAa,MAAKD,EAAaC,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAEpFG,IAAgBR;AAAA,MACpB,MAAMR,EAAK,MAAM,OAAO,CAAAa,MAAKI,GAAgBJ,GAAGV,EAAS,KAAK,CAAC,EAAE;AAAA,IAAA;AAGnE,aAASe,KAAQ;;AACf,MAAA3B,EAAK,QAAQ,GACbE,EAAQ,QAAQ,IAChBC,EAAK,QAAQ,MACbC,EAAS,QAAQ,CAAA,GACjBC,EAAQ,QAAQ,CAAA,GAChBE,EAAQ,QAAQ,CAAA,GAChBC,EAAQ,QAAQ,CAAA,GAChBC,EAAK,QAAQ,CAAA,GACbC,EAAa,QAAQ,CAAA,GACrBC,EAAW,QAAQ,IAEnBC,EAAS,QAAQI,EAAgB,MAAM,KAAK,CAAAY,MAAKA,EAAE,UAAUf,EAAkB,IAAI,IAC/EA,EAAkB,SACjBgB,IAAAb,EAAgB,MAAM,CAAC,MAAvB,gBAAAa,EAA0B,UAAShB,EAAkB,MAC1DC,EAAa,QAAQ;AAAA,IACvB;AAEA,IAAAgB,GAAMxC,GAAM,CAAAyC,MAAK;AACf,MAAIA,KAAGJ,GAAA;AAAA,IACT,CAAC;AAED,aAASK,EAAaC,GAAkB;AACtC,MAAA5B,EAAQ,QAAQ,CAAC,GAAG4B,EAAE,OAAO,GAC7B1B,EAAQ,QAAQ,EAAE,GAAG0B,EAAE,QAAA,GACvBzB,EAAQ,QAAQyB,EAAE,SAClBxB,EAAK,QAAQwB,EAAE,KAAK,IAAI,CAAAX,OAAM;AAAA,QAC5B,OAAOA,EAAE;AAAA,QACT,OAAO,EAAE,GAAGA,EAAE,MAAA;AAAA,QACd,QAAQ,CAAC,GAAGA,EAAE,MAAM;AAAA,MAAA,EACpB,GACFZ,EAAa,QAAQuB,EAAE;AAAA,IACzB;AAEA,mBAAeC,KAAqB;AAClC,MAAAhC,EAAQ,QAAQ;AAChB,UAAI;AACF,cAAMiC,IAAO,MAAM1C,EAAM,IAAI,iBAAA;AAC7B,QAAA2C,GAAoBD,GAAM1C,EAAM,oBAAoB,sBAAsB;AAAA,MAC5E,SAAS,GAAG;AACV,QAAAK,EAAQ,MAAMuC,EAAe,CAAC,CAAC;AAAA,MACjC,UAAA;AACE,QAAAnC,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,aAASoC,GAAaC,GAAwB;;AAE5C,MAAAnC,EAAS,QAAQmC,EAAK,MAAM,EAAE;AAC9B,YAAMC,KAAIX,IAAAU,EAAKA,EAAK,SAAS,CAAC,MAApB,gBAAAV,EAAuB;AACjC,MAAA1B,EAAK,QAAQqC,aAAa,OAAOA,IAAI;AAAA,IACvC;AAEA,aAASC,KAAe;AACtB,MAAArC,EAAS,QAAQ,CAAA,GACjBD,EAAK,QAAQ;AAAA,IACf;AAGA,mBAAeuC,KAAY;AACzB,UAAI,CAACvC,EAAK,OAAO;AACf,QAAAL,EAAQ,QAAQF,EAAE,iBAAiB,CAAC;AACpC;AAAA,MACF;AACA,MAAAM,EAAQ,QAAQ;AAChB,UAAI;AACF,cAAM+B,IAAI,MAAMxC,EAAM,IAAI,QAAQU,EAAK,KAAK;AAC5C,QAAA6B,EAAaC,CAAC;AAEd,mBAAWU,KAAUV,EAAE;AACrB,UAAMU,KAAUpC,EAAQ,UAAQA,EAAQ,MAAMoC,CAAM,IAAI;AAE1D,QAAA3C,EAAK,QAAQ;AAAA,MACf,SAAS,GAAG;AACV,QAAAF,EAAQ,MAAMuC,EAAe,CAAC,CAAC;AAAA,MACjC,UAAA;AACE,QAAAnC,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAGA,mBAAe0C,KAAY;AACzB,UAAI,CAACzC,EAAK,MAAO;AAEjB,YAAM0C,IAA8B,CAAA;AACpC,iBAAW,CAACF,GAAQG,CAAG,KAAK,OAAO,QAAQvC,EAAQ,KAAK;AACtD,QAAIuC,MAAKD,EAAIF,CAAM,IAAIG;AAEzB,MAAA5C,EAAQ,QAAQ;AAChB,UAAI;AACF,cAAM+B,IAAI,MAAMxC,EAAM,IAAI,QAAQU,EAAK,OAAO0C,CAAG;AACjD,QAAAb,EAAaC,CAAC,GACdjC,EAAK,QAAQ;AAAA,MACf,SAAS+C,GAAG;AACV,QAAAjD,EAAQ,MAAMuC,EAAeU,CAAC,CAAC;AAAA,MACjC,UAAA;AACE,QAAA7C,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,UAAM8C,KAAsB/B,EAAS,MAAM;AAAA,MACzC,EAAE,OAAOrB,EAAE,cAAc,GAAG,OAAO,GAAA;AAAA,MACnC,GAAGY,EAAQ,MAAM,IAAI,CAAAyC,OAAM;AAAA,QACzB,OAAOA,EAAE,WAAW,GAAGA,EAAE,KAAK,OAAOA,EAAE;AAAA,QACvC,OAAOA,EAAE;AAAA,MAAA,EACT;AAAA,IAAA,CACH;AAED,aAASC,GAAUC,GAAgBL,GAAa;AAC9C,aAAOK,EAAI,OAAO,KAAK,CAAAJ,MAAKA,EAAE,cAAcD,CAAG;AAAA,IACjD;AAEA,aAASM,EAAQD,GAAgBL,GAAa5B,GAAsB;AAClE,MAAAiC,EAAI,MAAML,CAAG,IAAI5B,GAEjBiC,EAAI,SAASA,EAAI,OAAO,OAAO,CAAAJ,MAAKA,EAAE,cAAcD,CAAG;AAAA,IACzD;AAEA,UAAMO,KAAcpC;AAAA,MAAS,MAC3BN,EAAW,QAAQF,EAAK,MAAM,OAAO,CAAAa,MAAKD,EAAaC,CAAC,EAAE,SAAS,CAAC,IAAIb,EAAK;AAAA,IAAA,GAGzE6C,KAAiBrC,EAAsC,MAAM;AACjE,YAAMsC,IAAoC;AAAA,QACxC;AAAA,UACE,OAAO;AAAA,UACP,KAAK;AAAA,UACL,OAAO;AAAA,UACP,OAAO;AAAA,UACP,QAAQ,OAAKjC,EAAE;AAAA,QAAA;AAAA,MACjB;AAEF,iBAAWkC,KAAOhD,EAAQ;AACxB,QAAA+C,EAAK,KAAK;AAAA,UACR,OAAO,MAAOC,EAAI,WAAW,GAAGA,EAAI,KAAK,OAAOA,EAAI;AAAA,UACpD,KAAKA,EAAI;AAAA,UACT,UAAU;AAAA,UACV,UAAU,EAAE,SAAS,GAAA;AAAA,UACrB,QAAQ,CAAAL,MAAO;AACb,kBAAMM,IAAMP,GAAUC,GAAKK,EAAI,GAAG,GAE5BE,IAAOD,MAAQ,UAAa,CAACtC,GAAOsC,CAAG,GACvCE,KAAMR,EAAI,MAAMK,EAAI,GAAG,KAAK,MAC5BI,KAASJ,EAAI,eACfK,EAAEC,IAAY;AAAA,cACZ,UAAUN,EAAI;AAAA,cACd,OAAOG;AAAA,cACP,WAAW,CAACH,EAAI;AAAA,cAChB,MAAM;AAAA;AAAA;AAAA;AAAA,cAIN,qBAAqB;AAAA,cACrB,OAAO,EAAE,OAAO,OAAA;AAAA,cAChB,QAAQC,KAAO,CAACC,IAAO,UAAU;AAAA,cACjC,kBAAkB,CAAC3B,MAAqBqB,EAAQD,GAAKK,EAAI,KAAKzB,CAAC;AAAA,YAAA,CAChE,IACD8B,EAAEE,IAAQ;AAAA,cACR,OAAOJ,MAAO;AAAA,cACd,MAAM;AAAA,cACN,QAAQF,KAAO,CAACC,IAAO,UAAU;AAAA,cACjC,eAAe,CAAC3B,MAAcqB,EAAQD,GAAKK,EAAI,KAAKzB,CAAC;AAAA,YAAA,CACtD,GACCiC,KAAOH;AAAA,cACX;AAAA,cACA;AAAA,gBACE,OAAQJ,IAEJC,IACE,iCACA,mCAHF;AAAA,cAGE;AAAA,cAER,CAACE,EAAM;AAAA,YAAA;AAET,mBAAKH,IACEI;AAAA,cACLI;AAAA,cACA,EAAE,SAAS,QAAA;AAAA,cACX;AAAA,gBACE,SAAS,MAAMD;AAAA,gBACf,SAAS,MACPN,IAAO9D,EAAE,kBAAkBgB,EAAS,KAAK,EAAE,IAAIyB,EAAeoB,EAAI,IAAI;AAAA,cAAA;AAAA,YAC1E,IAReO;AAAA,UAUnB;AAAA,QAAA,CACD;AAEH,aAAAT,EAAK,KAAK;AAAA,QACR,OAAO,MAAM3D,EAAE,eAAe;AAAA,QAC9B,KAAK;AAAA,QACL,OAAO;AAAA,QACP,OAAO;AAAA,QACP,QAAQ,CAAA0B,MAAK;AACX,cAAIA,EAAE,OAAO,WAAW;AACtB,mBAAOuC,EAAEK,GAAM,EAAE,MAAM,SAAS,MAAM,WAAW,UAAU,GAAA,GAAS,MAAMtE,EAAE,WAAW,CAAC;AAC1F,gBAAMuE,IAAO9C,EAAaC,CAAC;AAE3B,iBAAI6C,EAAK,WAAW,IACXN;AAAA,YACLI;AAAA,YACA,EAAE,SAAS,QAAA;AAAA,YACX;AAAA,cACE,SAAS,MACPJ;AAAA,gBAAEK;AAAA,gBAAM,EAAE,MAAM,SAAS,MAAM,WAAW,UAAU,GAAA;AAAA,gBAAS,MAC3DtE,EAAE,eAAe;AAAA,cAAA;AAAA,cAErB,SAAS,MAAMA,EAAE,kBAAkBgB,EAAS,KAAK,EAAE;AAAA,YAAA;AAAA,UACrD,IAGGiD;AAAA,YACLI;AAAA,YACA,EAAE,SAAS,QAAA;AAAA,YACX;AAAA,cACE,SAAS,MACPJ;AAAA,gBAAEK;AAAA,gBAAM,EAAE,MAAM,SAAS,MAAM,SAAS,UAAU,GAAA;AAAA,gBAAS,MACzDtE,EAAE,qBAAqB,EAAE,GAAGuE,EAAK,QAAQ;AAAA,cAAA;AAAA,cAE7C,SAAS,MAAMA,EAAK,IAAI,CAAApB,MAAK,GAAGA,EAAE,SAAS,KAAKV,EAAeU,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK;AAAA,CAAI;AAAA,YAAA;AAAA,UACrF;AAAA,QAEJ;AAAA,MAAA,CACD,GACMQ;AAAA,IACT,CAAC;AAED,mBAAea,KAAa;AAE1B,MAAAlE,EAAQ,QAAQ;AAChB,UAAI;AACF,cAAM+B,IAAI,MAAMxC,EAAM,IAAI,SAASgB,EAAK,KAAK;AAE7C,QAAAA,EAAK,QAAQwB,EAAE,KAAK,IAAI,CAAAX,OAAM;AAAA,UAC5B,OAAOA,EAAE;AAAA,UACT,OAAO,EAAE,GAAGA,EAAE,MAAA;AAAA,UACd,QAAQ,CAAC,GAAGA,EAAE,MAAM;AAAA,QAAA,EACpB,GACFd,EAAQ,QAAQyB,EAAE,QAAQ,SAASA,EAAE,UAAUzB,EAAQ,OAEvDV,EAAQ,QAAQF,EAAE,sBAAsB,EAAE,QAAQ4B,EAAU,OAAO,OAAOS,EAAE,MAAA,CAAO,CAAC;AAAA,MACtF,SAAS,GAAG;AACV,QAAAnC,EAAQ,MAAMuC,EAAe,CAAC,CAAC;AAAA,MACjC,UAAA;AACE,QAAAnC,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,mBAAemE,KAAW;AACxB,MAAAnE,EAAQ,QAAQ;AAChB,UAAI;AACF,cAAMoE,IAAS,MAAM7E,EAAM,IAAI,OAAOgB,EAAK,OAAOG,EAAS,KAAK;AAChE,QAAAE,EAAa,QAAQwD,GACrBtE,EAAK,QAAQ,GACTsE,EAAO,WAAWA,EAAO,UAAU,QAAQ,MAAM;AAAA,MACvD,SAAS,GAAG;AACV,QAAAxE,EAAQ,MAAMuC,EAAe,CAAC,CAAC;AAAA,MACjC,UAAA;AACE,QAAAnC,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAGA,aAASqE,KAAqB;AAC5B,MAAKzD,EAAa,UAClBL,EAAK,QAAQK,EAAa,MAAM,SAAS,IAAI,CAAAQ,OAAM;AAAA,QACjD,OAAOA,EAAE;AAAA,QACT,OAAO,EAAE,GAAGA,EAAE,MAAA;AAAA,QACd,QAAQ,CAAC,GAAGA,EAAE,MAAM;AAAA,MAAA,EACpB,GACFX,EAAW,QAAQ,IACnBG,EAAa,QAAQ,MACrBd,EAAK,QAAQ;AAAA,IACf;AAEA,mBAAewE,KAAsB;AACnC,YAAMC,IACJzE,EAAK,UAAU,KAAKc,EAAa,QAC7BA,EAAa,MAAM,WACnBL,EAAK,MAAM,OAAO,CAAAa,MAAKA,EAAE,OAAO,SAAS,CAAC;AAChD,UAAImD,EAAO,WAAW,GAAG;AACvB,QAAA3E,EAAQ,QAAQF,EAAE,oBAAoB,CAAC;AACvC;AAAA,MACF;AACA,MAAAM,EAAQ,QAAQ;AAChB,UAAI;AACF,cAAMiC,IAAO,MAAM1C,EAAM,IAAI,YAAYgF,CAAM;AAC/C,QAAArC,GAAoBD,GAAM1C,EAAM,uBAAuB,oBAAoB;AAAA,MAC7E,SAASsD,GAAG;AACV,QAAAjD,EAAQ,MAAMuC,EAAeU,CAAC,CAAC;AAAA,MACjC,UAAA;AACE,QAAA7C,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,aAASwE,KAAQ;AACf,MAAApF,EAAK,QAAQ;AAAA,IACf;2BAIEqF,EA4KUC,EAAAC,EAAA,GAAA;AAAA,MA3KA,MAAMvF,EAAA;AAAA,8CAAAA,EAAI,QAAAwF;AAAA,MAClB,QAAO;AAAA,MACN,OAAOrF,EAAM,SAASmF,EAAAhF,CAAA,EAAC,oBAAA;AAAA,MACvB,OAAO,EAAA,OAAA,oBAAA;AAAA,MACP,iBAAe;AAAA,MACf,WAAWM,EAAA;AAAA,MACX,iBAAeA,EAAA;AAAA,IAAA;MAyIL,UACT,MAyBU;AAAA,QAzBV6E,EAyBUH,EAAAI,CAAA,GAAA,EAzBD,SAAQ,SAAK;AAAA,qBACpB,MAEW;AAAA,YAFXD,EAEWH,EAAAK,CAAA,GAAA;AAAA,cAFA,UAAU/E,EAAA;AAAA,cAAU,SAAOwE;AAAA,YAAA;yBACpC,MAAyD;AAAA,oBAAtD1E,EAAA,UAAI,IAAS4E,EAAAhF,CAAA,EAAC,cAAA,IAAmBgF,EAAAhF,CAAA,EAAC,eAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAEvBI,EAAA,UAAI,UAApB2E,EAEWC,EAAAK,CAAA,GAAA;AAAA;cAFiB,WAAA;AAAA,cAAW,UAAU/E,EAAA;AAAA,cAAU,SAAOsE;AAAA,YAAA;yBAChE,MAAqC;AAAA,oBAAlCI,EAAAhF,CAAA,EAAC,4BAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAEUI,EAAA,aAAYA,EAAA,QAAI,UAAhC2E,EAEWC,EAAAK,CAAA,GAAA;AAAA;cAF4B,UAAU/E,EAAA;AAAA,cAAU,SAAKgF,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAA,CAAAJ,MAAE9E,EAAA,QAAOA,EAAA,QAAI;AAAA,YAAA;yBAC3E,MAAsB;AAAA,oBAAnB4E,EAAAhF,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAGEI,EAAA,UAAI,UADZ2E,EAQWC,EAAAK,CAAA,GAAA;AAAA;cANT,MAAK;AAAA,cACJ,SAAS/E,EAAA;AAAA,cACT,WAAWC,EAAA;AAAA,cACX,SAAOuC;AAAA,YAAA;yBAER,MAAsB;AAAA,oBAAnBkC,EAAAhF,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAEUI,EAAA,UAAI,UAApB2E,EAEWC,EAAAK,CAAA,GAAA;AAAA;cAFiB,MAAK;AAAA,cAAW,SAAS/E,EAAA;AAAA,cAAU,SAAO0C;AAAA,YAAA;yBACpE,MAAsB;AAAA,oBAAnBgC,EAAAhF,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAEUI,EAAA,UAAI,UAApB2E,EAEWC,EAAAK,CAAA,GAAA;AAAA;cAFiB,MAAK;AAAA,cAAW,SAAS/E,EAAA;AAAA,cAAU,SAAOmE;AAAA,YAAA;yBACpE,MAAwB;AAAA,oBAArBO,EAAAhF,CAAA,EAAC,eAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;iBA/JV,MAKU;AAAA,QALVmF,EAKUH,EAAAO,EAAA,GAAA;AAAA,UALA,SAASnF,EAAA;AAAA,UAAM,MAAK;AAAA,UAAQ,OAAM;AAAA,QAAA;qBAC1C,MAA0C;AAAA,YAA1C+E,EAA0CH,EAAAQ,CAAA,GAAA;AAAA,cAAjC,OAAOR,EAAAhF,CAAA,EAAC,mBAAA;AAAA,YAAA;YACjBmF,EAA2CH,EAAAQ,CAAA,GAAA;AAAA,cAAlC,OAAOR,EAAAhF,CAAA,EAAC,oBAAA;AAAA,YAAA;YACjBmF,EAA2CH,EAAAQ,CAAA,GAAA;AAAA,cAAlC,OAAOR,EAAAhF,CAAA,EAAC,oBAAA;AAAA,YAAA;YACjBmF,EAA0CH,EAAAQ,CAAA,GAAA;AAAA,cAAjC,OAAOR,EAAAhF,CAAA,EAAC,mBAAA;AAAA,YAAA;;;;QAInByF,EAAAC,EAyBM,OAzBNC,IAyBM;AAAA,UAxBJR,EAuBUH,EAAAI,CAAA,GAAA;AAAA,YAvBD,UAAA;AAAA,YAAU,MAAM;AAAA,UAAA;uBACvB,MAGW;AAAA,cAHXD,EAGWH,EAAAK,CAAA,GAAA;AAAA,gBAHD,WAAA;AAAA,gBAAW,SAAS/E,EAAA;AAAA,gBAAU,SAAOgC;AAAA,cAAA;gBAClC,QAAK,MAAgD;AAAA,kBAAhD6C,EAAgDS,GAAA;AAAA,oBAAvC,MAAK;AAAA,oBAAsB,MAAM;AAAA,kBAAA;;2BAAiB,MAC3E;AAAA,kBAD2EC,EAAA,QACxEb,EAAAhF,CAAA,EAAC,yBAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;cAENmF,EAiBWH,EAAAc,EAAA,GAAA;AAAA,gBAhBR,aAAWtF,EAAA;AAAA,gBACX,KAAK;AAAA,gBACN,QAAO;AAAA,gBACN,kBAAgB;AAAA,gBAChB,qBAAkBkC;AAAA,gBAClB,UAAQG;AAAA,cAAA;2BAET,MAQmB;AAAA,kBARnBsC,EAQmBH,EAAAe,EAAA,GAAA,MAAA;AAAA,+BAPjB,MAEM;AAAA,sBAFNL,EAEM,OAFNM,IAEM;AAAA,wBADJb,EAAyCS,GAAA;AAAA,0BAAhC,MAAK;AAAA,0BAAe,MAAM;AAAA,wBAAA;;sBAErCT,EAAmEH,EAAAiB,CAAA,GAAA,EAA3D,OAAA,EAAA,aAAA,OAAA,KAAuB;AAAA,mCAAC,MAA0B;AAAA,8BAAvBjB,EAAAhF,CAAA,EAAC,iBAAA,CAAA,GAAA,CAAA;AAAA,wBAAA;;;sBACpCmF,EAESH,EAAAiB,CAAA,GAAA;AAAA,wBAFD,OAAM;AAAA,wBAAI,OAAA,EAAA,SAAA,SAAA,cAAA,MAAA;AAAA,sBAAA;mCAChB,MAAyB;AAAA,8BAAtBjB,EAAAhF,CAAA,EAAC,gBAAA,CAAA,GAAA,CAAA;AAAA,wBAAA;;;;;;;;;;;;;cApBDI,EAAA,UAAI,CAAA;AAAA,QAAA;QA4BjBqF,EAAAC,EAsBM,OAtBNQ,IAsBM;AAAA,UArBWpF,EAAA,MAAa,eAA5BiE,EAEUC,EAAAmB,EAAA,GAAA;AAAA;YAF0B,MAAK;AAAA,YAAU,OAAA,EAAA,iBAAA,OAAA;AAAA,UAAA;uBACjD,MAAmF;AAAA,cAAhFN,EAAAO,EAAAtF,EAAA,MAAa,IAAI,CAAAqC,SAAQA,EAAE,SAAS,KAAK6B,KAAe7B,EAAE,IAAI,KAAK,KAAI,IAAA,CAAA,GAAA,CAAA;AAAA,YAAA;;;UAE5EgC,EAESH,EAAAiB,CAAA,GAAA;AAAA,YAFD,OAAM;AAAA,YAAI,OAAA,EAAA,SAAA,SAAA,iBAAA,OAAA;AAAA,UAAA;uBAChB,MAA6B;AAAA,kBAA1BjB,EAAAhF,CAAA,EAAC,oBAAA,CAAA,GAAA,CAAA;AAAA,YAAA;;;UAEN0F,EAcM,OAdNW,IAcM;AAAA,YAbJX,EAGM,OAHNY,IAGM;AAAA,cAFJZ,EAAyC,gBAAhCV,EAAAhF,CAAA,EAAC,mBAAA,CAAA,GAAA,CAAA;AAAA,cACV0F,EAA2C,gBAAlCV,EAAAhF,CAAA,EAAC,qBAAA,CAAA,GAAA,CAAA;AAAA,YAAA;oBAEZuG,EAQMC,GAAA,MAAAC,GARahG,EAAA,OAAO,CAAdiG,YAAZH,EAQM,OAAA;AAAA,cARuB,KAAKG;AAAA,cAAK,OAAM;AAAA,YAAA;cAC3ChB,EAAiE,QAAA;AAAA,gBAA3D,OAAM;AAAA,gBAAyB,OAAOgB;AAAA,cAAA,KAAQA,CAAG,GAAA,GAAAC,EAAA;AAAA,cACvDxB,EAKEH,EAAA4B,EAAA,GAAA;AAAA,gBAJQ,OAAOjG,EAAA,MAAQ+F,CAAG;AAAA,gBAAX,kBAAA,CAAAxB,MAAAvE,EAAA,MAAQ+F,CAAG,IAAAxB;AAAA,gBACzB,SAAS9B,GAAA;AAAA,gBACT,yBAAuB;AAAA,gBACxB,MAAK;AAAA,cAAA;;;;cAlBAhD,EAAA,UAAI,CAAA;AAAA,QAAA;QAyBjBqF,EAAAC,EAuCM,OAvCNmB,IAuCM;AAAA,UAtCJ1B,EAmBUH,EAAAI,CAAA,GAAA;AAAA,YAnBD,SAAQ;AAAA,YAAgB,OAAM;AAAA,YAAS,OAAA,EAAA,iBAAA,OAAA;AAAA,YAA4B,MAAA;AAAA,UAAA;uBAC1E,MAWU;AAAA,cAXVD,EAWUH,EAAAI,CAAA,GAAA,EAXD,OAAM,YAAQ;AAAA,2BACrB,MAKS;AAAA,kBALTD,EAKSH,EAAAiB,CAAA,GAAA,MAAA;AAAA,+BAJP,MAAoE;AAAA,sBAAjEJ,EAAAO,EAAApB,EAAAhF,CAAA,6BAA6Ba,EAAA,MAAK,QAAM,QAAUe,EAAA,MAAA,MAAe,KACpE,CAAA;AAAA,sBAAgBC,EAAA,cAAhB0E,EAEWC,GAAA,EAAA,KAAA,KAAA;AAAA,wBADNX,EAAAO,EAAApB,EAAAhF,CAAA,4BAA4B6B,EAAA,MAAA,CAAa,CAAA,GAAA,CAAA;AAAA,sBAAA;;;;kBAGhDsD,EAGWH,EAAA8B,EAAA,GAAA;AAAA,oBAHO,OAAO/F,EAAA;AAAA,6DAAAA,EAAU,QAAAmE;AAAA,oBAAE,MAAK;AAAA,kBAAA;oBAC7B,WAAQ,MAA4B;AAAA,0BAAzBF,EAAAhF,CAAA,EAAC,mBAAA,CAAA,GAAA,CAAA;AAAA,oBAAA;oBACZ,aAAU,MAA4B;AAAA,0BAAzBgF,EAAAhF,CAAA,EAAC,mBAAA,CAAA,GAAA,CAAA;AAAA,oBAAA;;;;;;cAG7BmF,EAKUH,EAAAI,CAAA,GAAA,MAAA;AAAA,2BAJR,MAGW;AAAA,kBAHXD,EAGWH,EAAAK,CAAA,GAAA;AAAA,oBAHD,MAAK;AAAA,oBAAS,SAAS/E,EAAA;AAAA,oBAAU,SAAOkE;AAAA,kBAAA;oBACrC,QAAK,MAAiD;AAAA,sBAAjDW,EAAiDS,GAAA;AAAA,wBAAxC,MAAK;AAAA,wBAAuB,MAAM;AAAA,sBAAA;;+BAAiB,MAC5E;AAAA,sBAD4EC,EAAA,QACzEb,EAAAhF,CAAA,EAAC,mBAAA,CAAA,GAAA,CAAA;AAAA,oBAAA;;;;;;;;;UAIVmF,EAOEH,EAAA+B,EAAA,GAAA;AAAA,YANA,MAAK;AAAA,YACJ,SAASrD,GAAA;AAAA,YACT,MAAMD,GAAA;AAAA,YACN,WAAO,CAAG,MAAiB,EAAE;AAAA,YAC7B,cAAY;AAAA,YACZ,YAAU,KAAK,IAAG,KAAM7C,EAAA,MAAQ,SAAM,MAAA,GAAA;AAAA,UAAA;UAEzC8E,EASM,OATNsB,IASM;AAAA,YARJ7B,EAA6EH,EAAAiB,CAAA,GAAA;AAAA,cAArE,QAAA;AAAA,cAAO,OAAA,EAAA,gBAAA,OAAA;AAAA,YAAA;yBAA2B,MAA0B;AAAA,oBAAvBjB,EAAAhF,CAAA,EAAC,iBAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;YAC9CmF,EAMgBH,EAAAiC,EAAA,GAAA;AAAA,cANO,OAAOjG,EAAA;AAAA,uDAAAA,EAAQ,QAAAkE;AAAA,cAAE,MAAK;AAAA,YAAA;yBAC3C,MAIU;AAAA,gBAJVC,EAIUH,EAAAI,CAAA,GAAA,MAAA;AAAA,6BAHC,MAAiC;AAAA,4BAA1CmB,EAEUC,GAAA,MAAAC,GAFgBrF,EAAA,OAAe,CAAzB8F,YAAhBnC,EAEUC,EAAAmC,EAAA,GAAA;AAAA,sBAFkC,KAAKD,EAAO;AAAA,sBAAQ,OAAOA,EAAO;AAAA,oBAAA;iCAC5E,MAAoB;AAAA,wBAAjBrB,EAAAO,EAAAc,EAAO,MAAA,CAAK,GAAA,CAAA;AAAA,sBAAA;;;;;;;;;;;cAlCZ9G,EAAA,UAAI,CAAA;AAAA,QAAA;QA0CjBqF,EAAAC,EA8BM,OA9BN0B,IA8BM;AAAA,UA5BIlG,EAAA,cADR6D,EAUUC,EAAAmB,EAAA,GAAA;AAAA;YARP,MAAMjF,EAAA,MAAa,SAAM,IAAA,YAAA;AAAA,YACzB,OAAO8D,EAAAhF,CAAA,EAAC,oBAAA;AAAA,UAAA;uBAET,MAAmE;AAAA,cAAnE0F,EAAmE,OAAA,MAAAU,EAA3DpB,EAAAhF,CAAA,EAAC,sBAAA,EAAA,GAA4BkB,EAAA,MAAa,MAAA,CAAK,CAAA,GAAA,CAAA;AAAA,cACvDwE,EAAyE,OAAA,MAAAU,EAAjEpB,EAAAhF,CAAA,EAAC,yBAAA,EAAA,GAA+BkB,EAAA,MAAa,SAAA,CAAQ,CAAA,GAAA,CAAA;AAAA,cAC7DwE,EAAuE,OAAA,MAAAU,EAA/DpB,EAAAhF,CAAA,EAAC,wBAAA,EAAA,GAA8BkB,EAAA,MAAa,QAAA,CAAO,CAAA,GAAA,CAAA;AAAA,cAC3DwE,EAAuE,OAAA,MAAAU,EAA/DpB,EAAAhF,CAAA,EAAC,wBAAA,EAAA,GAA8BkB,EAAA,MAAa,QAAA,CAAO,CAAA,GAAA,CAAA;AAAA,cAC3DwE,EAAqE,OAAA,MAAAU,EAA7DpB,EAAAhF,CAAA,EAAC,uBAAA,EAAA,GAA6BkB,EAAA,MAAa,OAAA,CAAM,CAAA,GAAA,CAAA;AAAA,YAAA;;;UAE3DiE,EAiBUH,EAAAI,CAAA,GAAA,EAjBD,OAAA,EAAA,cAAA,OAAA,KAAwB;AAAA,uBAC/B,MAMW;AAAA,cALHlE,EAAA,SAAgBA,EAAA,MAAa,SAAM,UAD3C6D,EAMWC,EAAAK,CAAA,GAAA;AAAA;gBAJT,MAAK;AAAA,gBACJ,SAAOV;AAAA,cAAA;2BAER,MAA8B;AAAA,sBAA3BK,EAAAhF,CAAA,EAAC,qBAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;cAGEkB,EAAA,SAAgBA,EAAA,MAAa,SAAM,UAD3C6D,EAQWC,EAAAK,CAAA,GAAA;AAAA;gBANT,WAAA;AAAA,gBACC,SAAS/E,EAAA;AAAA,gBACT,SAAOsE;AAAA,cAAA;gBAEG,QAAK,MAAgD;AAAA,kBAAhDO,EAAgDS,GAAA;AAAA,oBAAvC,MAAK;AAAA,oBAAsB,MAAM;AAAA,kBAAA;;2BAAiB,MAC3E;AAAA,kBAD2EC,EAAA,QACxEb,EAAAhF,CAAA,EAAC,4BAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;;;;;cA3BGI,EAAA,UAAI,CAAA;AAAA,QAAA;;;;;;"}
1
+ {"version":3,"file":"index2.vue.js","sources":["../../../src/components/ImportWizard/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 四步导入向导:①上传 ②列映射 ③预览改错(裸 n-data-table)④结果。\n// API 由父级注入(用户导入走 userApi 六方法),组件对资源无感知,可复用到其他档案。\nimport { computed, h, ref, shallowRef, watch } from 'vue'\nimport {\n NModal,\n NSteps,\n NStep,\n NUpload,\n NUploadDragger,\n NText,\n NButton,\n NSpace,\n NSelect,\n NDataTable,\n NInput,\n NSwitch,\n NRadioGroup,\n NRadio,\n NAlert,\n NTooltip,\n NTag,\n useMessage,\n type DataTableColumns,\n type UploadFileInfo,\n} from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport AppIcon from '#/components/AppIcon.vue'\nimport DictSelect from '#/components/DictSelect/index.vue'\nimport { translateError } from '#/utils/error'\nimport { triggerBlobDownload } from '#/utils/download'\nimport { hardErrorsOf as hardErrors, isDuplicateOnly, isHardError } from '#/utils/importDup'\nimport type {\n CellError,\n DuplicateStrategy as DupStrategyNum,\n ImportColumn,\n ImportCommitResult,\n ImportPreview,\n ImportRow,\n} from '#/types/api'\nimport { DuplicateStrategy } from '#/types/api'\n\nexport interface ImportWizardApi {\n downloadTemplate: () => Promise<Blob>\n preview: (file: File, mapping?: Record<string, string>) => Promise<ImportPreview>\n validate: (rows: ImportRow[]) => Promise<ImportPreview>\n commit: (rows: ImportRow[], strategy: DupStrategyNum) => Promise<ImportCommitResult>\n errorReport: (rows: ImportRow[]) => Promise<Blob>\n}\n\nconst show = defineModel<boolean>('show', { default: false })\n\nconst props = defineProps<{\n api: ImportWizardApi\n /**\n * 弹窗标题。缺省是 `import.wizardTitle`(「导入用户」)——那是内核用户导入的文案,\n * 本组件被别的档案复用时必须传自己的标题,否则会顶着「导入用户」导别的东西。\n */\n title?: string\n /** 模板下载默认文件名 */\n templateFileName?: string\n /** 错误报告默认文件名 */\n errorReportFileName?: string\n /**\n * 可选的重复策略,按给定顺序渲染;缺省三种都给。\n * 用于档案本身不支持某种策略的场景 —— 例如条码储位只允许「跳过已存在」,\n * 覆盖会把条码挪到别的库位,不是导入该干的事。\n */\n strategies?: DupStrategyNum[]\n}>()\n\nconst emit = defineEmits<{\n /** 提交成功(含部分成功)后触发,父级通常 refresh 列表 */\n done: []\n}>()\n\nconst { t } = useI18n()\nconst message = useMessage()\n\n// ── 状态 ──\nconst step = ref(1) // n-steps 1-based\nconst loading = ref(false)\nconst file = ref<File | null>(null)\nconst fileList = ref<UploadFileInfo[]>([])\nconst headers = shallowRef<string[]>([])\n/** 表头 → 列 Key(空串 = 不映射) */\nconst mapping = ref<Record<string, string>>({})\nconst columns = shallowRef<ImportColumn[]>([])\n// rows 刻意保持深响应:setCell 是就地改 row.cells / row.errors(可编辑预览表),\n// 换成 shallowRef 后单元格改完不会重渲,红底也不会消 —— 要改得先把编辑改成不可变替换。\nconst rows = ref<ImportRow[]>([])\nconst columnErrors = ref<ImportPreview['columnErrors']>([])\nconst onlyErrors = ref(false)\nconst strategy = ref<DupStrategyNum>(DuplicateStrategy.Skip)\nconst commitResult = ref<ImportCommitResult | null>(null)\n\n/** 策略标签用函数形式,切换语言即时生效(与 SmartTable 列 title 同理)。 */\nconst strategyLabels: Record<DupStrategyNum, () => string> = {\n [DuplicateStrategy.Skip]: () => t('import.strategySkip'),\n [DuplicateStrategy.Overwrite]: () => t('import.strategyOverwrite'),\n [DuplicateStrategy.Error]: () => t('import.strategyError'),\n}\n/** 未指定 strategies 时三种都给。 */\nconst strategyOptions = computed(() =>\n (\n props.strategies ?? [\n DuplicateStrategy.Skip,\n DuplicateStrategy.Overwrite,\n DuplicateStrategy.Error,\n ]\n ).map(value => ({ value, label: strategyLabels[value] })),\n)\n\n// 「已存在」按策略呈现的判定见 utils/importDup(那里也记着与后端的镜像关系)\nconst isHard = (e: CellError) => isHardError(e, strategy.value)\nconst hardErrorsOf = (r: ImportRow) => hardErrors(r, strategy.value)\n/** 需要用户动手改的行数(不含「已存在」——那些按策略自动处理)。 */\nconst errorRows = computed(() => rows.value.filter(r => hardErrorsOf(r).length > 0).length)\n/** 只是「已存在」、会被策略正常处理的行数。 */\nconst duplicateRows = computed(\n () => rows.value.filter(r => isDuplicateOnly(r, strategy.value)).length,\n)\n\nfunction reset() {\n step.value = 1\n loading.value = false\n file.value = null\n fileList.value = []\n headers.value = []\n mapping.value = {}\n columns.value = []\n rows.value = []\n columnErrors.value = []\n onlyErrors.value = false\n // 默认选 Skip;调用方限制了可选策略且不含 Skip 时退到第一个可选项,避免选中一个没渲染的值。\n strategy.value = strategyOptions.value.some(o => o.value === DuplicateStrategy.Skip)\n ? DuplicateStrategy.Skip\n : (strategyOptions.value[0]?.value ?? DuplicateStrategy.Skip)\n commitResult.value = null\n}\n\nwatch(show, v => {\n if (v) reset()\n})\n\nfunction applyPreview(p: ImportPreview) {\n headers.value = [...p.headers]\n mapping.value = { ...p.mapping }\n columns.value = p.columns\n rows.value = p.rows.map(r => ({\n index: r.index,\n cells: { ...r.cells },\n errors: [...r.errors],\n }))\n columnErrors.value = p.columnErrors\n}\n\nasync function onDownloadTemplate() {\n loading.value = true\n try {\n const blob = await props.api.downloadTemplate()\n triggerBlobDownload(blob, props.templateFileName ?? 'import-template.xlsx')\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\nfunction onFileChange(list: UploadFileInfo[]) {\n // 只保留最后选中的一个 xlsx\n fileList.value = list.slice(-1)\n const f = list[list.length - 1]?.file\n file.value = f instanceof File ? f : null\n}\n\nfunction onFileRemove() {\n fileList.value = []\n file.value = null\n}\n\n/** ①→②:带文件预览(mapping 空 = 服务端自动匹配) */\nasync function goMapping() {\n if (!file.value) {\n message.warning(t('import.needFile'))\n return\n }\n loading.value = true\n try {\n const p = await props.api.preview(file.value)\n applyPreview(p)\n // 确保每个表头在 mapping 里有键(未匹配的给空串,便于 n-select 显示「不映射」)\n for (const header of p.headers) {\n if (!(header in mapping.value)) mapping.value[header] = ''\n }\n step.value = 2\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\n/** ②→③:按用户调整后的映射重新预览 */\nasync function goPreview() {\n if (!file.value) return\n // 去掉「不映射」空串,只传有效映射\n const map: Record<string, string> = {}\n for (const [header, key] of Object.entries(mapping.value)) {\n if (key) map[header] = key\n }\n loading.value = true\n try {\n const p = await props.api.preview(file.value, map)\n applyPreview(p)\n step.value = 3\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\nconst targetColumnOptions = computed(() => [\n { label: t('import.unmap'), value: '' },\n ...columns.value.map(c => ({\n label: c.required ? `${c.title} *` : c.title,\n value: c.key,\n })),\n])\n\nfunction cellError(row: ImportRow, key: string) {\n return row.errors.find(e => e.columnKey === key)\n}\n\nfunction setCell(row: ImportRow, key: string, value: string | null) {\n row.cells[key] = value\n // 就地改后清该列旧错误,等重验;否则红底会误导\n row.errors = row.errors.filter(e => e.columnKey !== key)\n}\n\nconst displayRows = computed(() =>\n onlyErrors.value ? rows.value.filter(r => hardErrorsOf(r).length > 0) : rows.value,\n)\n\nconst previewColumns = computed<DataTableColumns<ImportRow>>(() => {\n const cols: DataTableColumns<ImportRow> = [\n {\n title: '#',\n key: '_index',\n width: 56,\n fixed: 'left',\n render: r => r.index,\n },\n ]\n for (const col of columns.value) {\n cols.push({\n title: () => (col.required ? `${col.title} *` : col.title),\n key: col.key,\n minWidth: 120,\n ellipsis: { tooltip: false },\n render: row => {\n const err = cellError(row, col.key)\n // 「已存在」在跳过/覆盖策略下不算错:走警示底色,输入框也不置 error 态\n const soft = err !== undefined && !isHard(err)\n const val = row.cells[col.key] ?? null\n const editor = col.dictTypeCode\n ? h(DictSelect, {\n typeCode: col.dictTypeCode,\n value: val,\n clearable: !col.required,\n size: 'small',\n // 弹层不跟随触发器宽度:向导的列很窄(minWidth 120,实测触发器约 66px),\n // 跟随会把「启用」「停用」截成「启..」「停..」。普通表单里下拉够宽,\n // 跟随反而更整齐 —— 所以只在向导关掉,不动共用的 DictSelect。\n consistentMenuWidth: false,\n style: { width: '100%' },\n status: err && !soft ? 'error' : undefined,\n 'onUpdate:value': (v: string | null) => setCell(row, col.key, v),\n })\n : h(NInput, {\n value: val ?? '',\n size: 'small',\n status: err && !soft ? 'error' : undefined,\n onUpdateValue: (v: string) => setCell(row, col.key, v),\n })\n const cell = h(\n 'div',\n {\n class: !err\n ? 'import-cell'\n : soft\n ? 'import-cell import-cell--dup'\n : 'import-cell import-cell--error',\n },\n [editor],\n )\n if (!err) return cell\n return h(\n NTooltip,\n { trigger: 'hover' },\n {\n trigger: () => cell,\n default: () =>\n soft ? t(`import.dupHint.${strategy.value}`) : translateError(err.code),\n },\n )\n },\n })\n }\n cols.push({\n title: () => t('import.errors'),\n key: '_errors',\n width: 160,\n fixed: 'right',\n render: r => {\n if (r.errors.length === 0)\n return h(NTag, { size: 'small', type: 'success', bordered: false }, () => t('import.ok'))\n const hard = hardErrorsOf(r)\n // 只剩「已存在」:不是要用户改的错,给警示标签 +「将按策略处理」而非红色错误数\n if (hard.length === 0) {\n return h(\n NTooltip,\n { trigger: 'hover' },\n {\n trigger: () =>\n h(NTag, { size: 'small', type: 'warning', bordered: false }, () =>\n t('import.dupTag'),\n ),\n default: () => t(`import.dupHint.${strategy.value}`),\n },\n )\n }\n return h(\n NTooltip,\n { trigger: 'hover' },\n {\n trigger: () =>\n h(NTag, { size: 'small', type: 'error', bordered: false }, () =>\n t('import.errorCount', { n: hard.length }),\n ),\n default: () => hard.map(e => `${e.columnKey}: ${translateError(e.code)}`).join('\\n'),\n },\n )\n },\n })\n return cols\n})\n\nasync function revalidate() {\n // 变异判据:请求必须带 rows;空数组也是明确失败路径(服务端会返回 0 行)\n loading.value = true\n try {\n const p = await props.api.validate(rows.value)\n // 保留当前列定义(重验不回传 headers/mapping;columns 仍有)\n rows.value = p.rows.map(r => ({\n index: r.index,\n cells: { ...r.cells },\n errors: [...r.errors],\n }))\n columns.value = p.columns.length ? p.columns : columns.value\n // 用本地按策略算出的硬错误数,不用 p.errorRows —— 后端不知道当前策略,会把「已存在」也计进去\n message.success(t('import.revalidated', { errors: errorRows.value, total: p.total }))\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\nasync function doCommit() {\n loading.value = true\n try {\n const result = await props.api.commit(rows.value, strategy.value)\n commitResult.value = result\n step.value = 4\n if (result.inserted + result.updated > 0) emit('done')\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\n/** ④ 失败行接回 ③ 继续改 */\nfunction backToEditFailures() {\n if (!commitResult.value) return\n rows.value = commitResult.value.failures.map(r => ({\n index: r.index,\n cells: { ...r.cells },\n errors: [...r.errors],\n }))\n onlyErrors.value = true\n commitResult.value = null\n step.value = 3\n}\n\nasync function downloadErrorReport() {\n const source =\n step.value === 4 && commitResult.value\n ? commitResult.value.failures\n : rows.value.filter(r => r.errors.length > 0)\n if (source.length === 0) {\n message.warning(t('import.noErrorRows'))\n return\n }\n loading.value = true\n try {\n const blob = await props.api.errorReport(source)\n triggerBlobDownload(blob, props.errorReportFileName ?? 'import-errors.xlsx')\n } catch (e) {\n message.error(translateError(e))\n } finally {\n loading.value = false\n }\n}\n\nfunction close() {\n show.value = false\n}\n</script>\n\n<template>\n <n-modal\n v-model:show=\"show\"\n preset=\"card\"\n :title=\"props.title ?? t('import.wizardTitle')\"\n :style=\"{ width: 'min(1100px, 96vw)' }\"\n :mask-closable=\"false\"\n :closable=\"!loading\"\n :close-on-esc=\"!loading\"\n >\n <n-steps :current=\"step\" size=\"small\" class=\"import-steps\">\n <n-step :title=\"t('import.stepUpload')\" />\n <n-step :title=\"t('import.stepMapping')\" />\n <n-step :title=\"t('import.stepPreview')\" />\n <n-step :title=\"t('import.stepResult')\" />\n </n-steps>\n\n <!-- ① 上传 -->\n <div v-show=\"step === 1\" class=\"import-pane\">\n <n-space vertical :size=\"12\">\n <n-button secondary :loading=\"loading\" @click=\"onDownloadTemplate\">\n <template #icon><AppIcon icon=\"ph:download-simple\" :size=\"16\" /></template>\n {{ t('import.downloadTemplate') }}\n </n-button>\n <n-upload\n :file-list=\"fileList\"\n :max=\"1\"\n accept=\".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\"\n :default-upload=\"false\"\n @update:file-list=\"onFileChange\"\n @remove=\"onFileRemove\"\n >\n <n-upload-dragger>\n <div class=\"import-upload-icon\">\n <AppIcon icon=\"ph:file-xls\" :size=\"40\" />\n </div>\n <n-text style=\"font-size: 16px\">{{ t('import.dropHint') }}</n-text>\n <n-text depth=\"3\" style=\"display: block; margin-top: 8px\">\n {{ t('import.dropSub') }}\n </n-text>\n </n-upload-dragger>\n </n-upload>\n </n-space>\n </div>\n\n <!-- ② 列映射 -->\n <div v-show=\"step === 2\" class=\"import-pane\">\n <n-alert v-if=\"columnErrors.length\" type=\"warning\" style=\"margin-bottom: 12px\">\n {{ columnErrors.map(e => `${e.columnKey}: ${translateError(e.code)}`).join('; ') }}\n </n-alert>\n <n-text depth=\"3\" style=\"display: block; margin-bottom: 12px\">\n {{ t('import.mappingHint') }}\n </n-text>\n <div class=\"import-mapping\">\n <div class=\"import-mapping-head\">\n <span>{{ t('import.fileHeader') }}</span>\n <span>{{ t('import.targetColumn') }}</span>\n </div>\n <div v-for=\"hdr in headers\" :key=\"hdr\" class=\"import-mapping-row\">\n <span class=\"import-mapping-header\" :title=\"hdr\">{{ hdr }}</span>\n <n-select\n v-model:value=\"mapping[hdr]\"\n :options=\"targetColumnOptions\"\n :consistent-menu-width=\"false\"\n size=\"small\"\n />\n </div>\n </div>\n </div>\n\n <!-- ③ 预览改错(裸 n-data-table,不用 SmartTable) -->\n <div v-show=\"step === 3\" class=\"import-pane\">\n <n-space justify=\"space-between\" align=\"center\" style=\"margin-bottom: 12px\" wrap>\n <n-space align=\"center\">\n <n-text>\n {{ t('import.summary', { total: rows.length, errors: errorRows }) }}\n <template v-if=\"duplicateRows\">\n {{ t('import.summaryDup', { n: duplicateRows }) }}\n </template>\n </n-text>\n <n-switch v-model:value=\"onlyErrors\" size=\"small\">\n <template #checked>{{ t('import.onlyErrors') }}</template>\n <template #unchecked>{{ t('import.onlyErrors') }}</template>\n </n-switch>\n </n-space>\n <n-space>\n <n-button size=\"small\" :loading=\"loading\" @click=\"revalidate\">\n <template #icon><AppIcon icon=\"ph:arrows-clockwise\" :size=\"14\" /></template>\n {{ t('import.revalidate') }}\n </n-button>\n </n-space>\n </n-space>\n <n-data-table\n size=\"small\"\n :columns=\"previewColumns\"\n :data=\"displayRows\"\n :row-key=\"(r: ImportRow) => r.index\"\n :max-height=\"420\"\n :scroll-x=\"Math.max(800, columns.length * 140 + 220)\"\n />\n <div class=\"import-strategy\">\n <n-text strong style=\"margin-right: 12px\">{{ t('import.strategy') }}</n-text>\n <n-radio-group v-model:value=\"strategy\" name=\"dup-strategy\">\n <n-space>\n <n-radio v-for=\"option in strategyOptions\" :key=\"option.value\" :value=\"option.value\">\n {{ option.label() }}\n </n-radio>\n </n-space>\n </n-radio-group>\n </div>\n </div>\n\n <!-- ④ 结果 -->\n <div v-show=\"step === 4\" class=\"import-pane\">\n <n-alert\n v-if=\"commitResult\"\n :type=\"commitResult.failed > 0 ? 'warning' : 'success'\"\n :title=\"t('import.resultTitle')\"\n >\n <div>{{ t('import.resultTotal', { n: commitResult.total }) }}</div>\n <div>{{ t('import.resultInserted', { n: commitResult.inserted }) }}</div>\n <div>{{ t('import.resultUpdated', { n: commitResult.updated }) }}</div>\n <div>{{ t('import.resultSkipped', { n: commitResult.skipped }) }}</div>\n <div>{{ t('import.resultFailed', { n: commitResult.failed }) }}</div>\n </n-alert>\n <n-space style=\"margin-top: 16px\">\n <n-button\n v-if=\"commitResult && commitResult.failed > 0\"\n type=\"warning\"\n @click=\"backToEditFailures\"\n >\n {{ t('import.editFailures') }}\n </n-button>\n <n-button\n v-if=\"commitResult && commitResult.failed > 0\"\n secondary\n :loading=\"loading\"\n @click=\"downloadErrorReport\"\n >\n <template #icon><AppIcon icon=\"ph:download-simple\" :size=\"16\" /></template>\n {{ t('import.downloadErrorReport') }}\n </n-button>\n </n-space>\n </div>\n\n <template #footer>\n <n-space justify=\"end\">\n <n-button :disabled=\"loading\" @click=\"close\">\n {{ step === 4 ? t('common.close') : t('common.cancel') }}\n </n-button>\n <n-button v-if=\"step === 3\" secondary :disabled=\"loading\" @click=\"downloadErrorReport\">\n {{ t('import.downloadErrorReport') }}\n </n-button>\n <n-button v-if=\"step > 1 && step < 4\" :disabled=\"loading\" @click=\"step = step - 1\">\n {{ t('import.prev') }}\n </n-button>\n <n-button\n v-if=\"step === 1\"\n type=\"primary\"\n :loading=\"loading\"\n :disabled=\"!file\"\n @click=\"goMapping\"\n >\n {{ t('import.next') }}\n </n-button>\n <n-button v-if=\"step === 2\" type=\"primary\" :loading=\"loading\" @click=\"goPreview\">\n {{ t('import.next') }}\n </n-button>\n <n-button v-if=\"step === 3\" type=\"primary\" :loading=\"loading\" @click=\"doCommit\">\n {{ t('import.commit') }}\n </n-button>\n </n-space>\n </template>\n </n-modal>\n</template>\n\n<style scoped>\n.import-steps {\n margin-bottom: 20px;\n}\n.import-pane {\n min-height: 200px;\n}\n.import-upload-icon {\n margin-bottom: 12px;\n color: var(--n-text-color-3);\n}\n.import-mapping-head,\n.import-mapping-row {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n align-items: center;\n}\n.import-mapping-head {\n font-weight: 600;\n font-size: 13px;\n margin-bottom: 8px;\n color: var(--n-text-color-2);\n}\n.import-mapping-row {\n margin-bottom: 8px;\n}\n.import-mapping-header {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13px;\n}\n.import-strategy {\n margin-top: 16px;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n:deep(.import-cell) {\n padding: 2px;\n border-radius: 4px;\n}\n:deep(.import-cell--error) {\n /* 用仓库自己的语义令牌(亮/暗都有定义)。别写 --n-error-color:那是 naive 组件内部的变量,\n 在这层普通 div 上未定义,整条 color-mix() 会被判无效丢弃 —— 红底静默不渲染,而 tsc/lint/build 全绿。 */\n background: var(--color-danger-bg);\n}\n:deep(.import-cell--dup) {\n /* 「库里已存在」在跳过/覆盖策略下会被正常处理,用警示色而非错误红——同上,只用语义令牌 */\n background: var(--color-warning-bg);\n}\n</style>\n"],"names":["show","_useModel","__props","props","emit","__emit","t","useI18n","message","useMessage","step","ref","loading","file","fileList","headers","shallowRef","mapping","columns","rows","columnErrors","onlyErrors","strategy","DuplicateStrategy","commitResult","strategyLabels","strategyOptions","computed","value","isHard","isHardError","hardErrorsOf","r","hardErrors","errorRows","duplicateRows","isDuplicateOnly","reset","o","_a","watch","v","applyPreview","p","onDownloadTemplate","blob","triggerBlobDownload","translateError","onFileChange","list","f","onFileRemove","goMapping","header","goPreview","map","key","e","targetColumnOptions","c","cellError","row","setCell","displayRows","previewColumns","cols","col","err","soft","val","editor","h","DictSelect","NInput","cell","NTooltip","NTag","hard","revalidate","doCommit","result","backToEditFailures","downloadErrorReport","source","close","_createBlock","_unref","NModal","$event","_createVNode","NSpace","NButton","_cache","NSteps","NStep","_withDirectives","_createElementVNode","_hoisted_1","AppIcon","_createTextVNode","NUpload","NUploadDragger","_hoisted_2","NText","_hoisted_3","NAlert","_toDisplayString","_hoisted_4","_hoisted_5","_createElementBlock","_Fragment","_renderList","hdr","_hoisted_6","NSelect","_hoisted_7","NSwitch","NDataTable","_hoisted_8","NRadioGroup","option","NRadio","_hoisted_9"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.vue.js","sources":["../../../src/components/TableZoomButton/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 表格「放大 / 还原」按钮。与 SmartTable 工具栏自带的刷新 / 密度 / 列设置同形态(circle quaternary + tooltip),\n// 放大态换收起图标并染主色 —— 不要写成带文字的 secondary 按钮,那在工具栏里一眼就是外来户。\n//\n// 状态由调用方的 useTableZoom() 持有(一块表一份实例),这里只负责外观与触发。\n// 按钮必须和表格一起进 Teleport 容器:留在 n-card 的 #header-extra 里,放大后会被遮罩盖住,\n// 还原按钮点不到,只能按 Esc。用法见 skills/create-page-variant.md 变体五。\nimport { NButton, NTooltip } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport AppIcon from '#/components/AppIcon.vue'\n\nwithDefaults(defineProps<{ zoomed: boolean; size?: 'tiny' | 'small' | 'medium' | 'large' }>(), {\n size: 'small',\n})\ndefineEmits<{ toggle: [] }>()\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <n-tooltip>\n <template #trigger>\n <n-button\n :size=\"size\"\n circle\n quaternary\n :type=\"zoomed ? 'primary' : 'default'\"\n :aria-label=\"zoomed ? t('table.zoomExit') : t('table.zoom')\"\n @click=\"$emit('toggle')\"\n >\n <template #icon>\n <AppIcon :icon=\"zoomed ? 'ph:corners-in' : 'ph:corners-out'\" />\n </template>\n </n-button>\n </template>\n {{ zoomed ? t('table.zoomExit') : t('table.zoom') }}\n </n-tooltip>\n</template>\n"],"names":["useI18n","_createBlock","_unref","NTooltip","_createVNode","NButton","__props","$emit","AppIcon","_createTextVNode","_toDisplayString"],"mappings":";;;;;;;;;;;;AAgBA,UAAM,EAAE,EAAA,IAAMA,EAAA;2BAIZC,EAgBYC,EAAAC,CAAA,GAAA,MAAA;AAAA,MAfC,WACT,MAWW;AAAA,QAXXC,EAWWF,EAAAG,CAAA,GAAA;AAAA,UAVR,MAAMC,EAAA;AAAA,UACP,QAAA;AAAA,UACA,YAAA;AAAA,UACC,MAAMA,EAAA,SAAM,YAAA;AAAA,UACZ,cAAYA,EAAA,SAASJ,EAAA,CAAA,sBAAsBA,EAAA,CAAA,EAAC,YAAA;AAAA,UAC5C,gCAAOK,EAAAA,MAAK,QAAA;AAAA,QAAA;UAEF,QACT,MAA+D;AAAA,YAA/DH,EAA+DI,GAAA;AAAA,cAArD,MAAMF,EAAA,SAAM,kBAAA;AAAA,YAAA;;;;;iBAGjB,MACX;AAAA,QADWG,EAAA,MACXC,EAAGJ,EAAA,SAASJ,EAAA,CAAA,sBAAsBA,EAAA,CAAA,EAAC,YAAA,CAAA,GAAA,CAAA;AAAA,MAAA;;;;;"}
1
+ {"version":3,"file":"index.vue.js","sources":["../../../src/components/TableZoomButton/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// 表格「放大 / 还原」按钮。与 SmartTable 工具栏自带的刷新 / 密度 / 列设置同形态(circle quaternary + tooltip),\n// 放大态换收起图标并染主色 —— 不要写成带文字的 secondary 按钮,那在工具栏里一眼就是外来户。\n//\n// 状态由调用方的 useTableZoom() 持有(一块表一份实例),这里只负责外观与触发。\n// 按钮必须和表格一起进 Teleport 容器:留在 n-card 的 #header-extra 里,放大后会被遮罩盖住,\n// 还原按钮点不到,只能按 Esc。用法见 skills/create-page-variant/split-zoom.md。\nimport { NButton, NTooltip } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport AppIcon from '#/components/AppIcon.vue'\n\nwithDefaults(defineProps<{ zoomed: boolean; size?: 'tiny' | 'small' | 'medium' | 'large' }>(), {\n size: 'small',\n})\ndefineEmits<{ toggle: [] }>()\n\nconst { t } = useI18n()\n</script>\n\n<template>\n <n-tooltip>\n <template #trigger>\n <n-button\n :size=\"size\"\n circle\n quaternary\n :type=\"zoomed ? 'primary' : 'default'\"\n :aria-label=\"zoomed ? t('table.zoomExit') : t('table.zoom')\"\n @click=\"$emit('toggle')\"\n >\n <template #icon>\n <AppIcon :icon=\"zoomed ? 'ph:corners-in' : 'ph:corners-out'\" />\n </template>\n </n-button>\n </template>\n {{ zoomed ? t('table.zoomExit') : t('table.zoom') }}\n </n-tooltip>\n</template>\n"],"names":["useI18n","_createBlock","_unref","NTooltip","_createVNode","NButton","__props","$emit","AppIcon","_createTextVNode","_toDisplayString"],"mappings":";;;;;;;;;;;;AAgBA,UAAM,EAAE,EAAA,IAAMA,EAAA;2BAIZC,EAgBYC,EAAAC,CAAA,GAAA,MAAA;AAAA,MAfC,WACT,MAWW;AAAA,QAXXC,EAWWF,EAAAG,CAAA,GAAA;AAAA,UAVR,MAAMC,EAAA;AAAA,UACP,QAAA;AAAA,UACA,YAAA;AAAA,UACC,MAAMA,EAAA,SAAM,YAAA;AAAA,UACZ,cAAYA,EAAA,SAASJ,EAAA,CAAA,sBAAsBA,EAAA,CAAA,EAAC,YAAA;AAAA,UAC5C,gCAAOK,EAAAA,MAAK,QAAA;AAAA,QAAA;UAEF,QACT,MAA+D;AAAA,YAA/DH,EAA+DI,GAAA;AAAA,cAArD,MAAMF,EAAA,SAAM,kBAAA;AAAA,YAAA;;;;;iBAGjB,MACX;AAAA,QADWG,EAAA,MACXC,EAAGJ,EAAA,SAASJ,EAAA,CAAA,sBAAsBA,EAAA,CAAA,EAAC,YAAA,CAAA,GAAA,CAAA;AAAA,MAAA;;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useConfirm.js","sources":["../../src/composables/useConfirm.ts"],"sourcesContent":["// 二次确认 + 结果 toast 的样板收敛(参考参照实现 useHandleData 的取舍):\n// ask —— 仅弹确认框不执行动作,resolve(用户是否确认);给需要自管后续流程的组件(如 StatusSwitch)。\n// confirm —— dialog 确认后执行 action,成/败统一 toast,resolve(执行是否成功);给不适合内联的重操作。\n// run —— 不弹 dialog 的后半段「执行 → toast」,配模板层 n-popconfirm 的 positive-click 用\n// (popconfirm 是触发器旁的内联组件,保留在模板层比 composable 化更顺手)。\n// 仅限 setup 中调用:useDialog/useMessage 依赖 App.vue 的 Provider 注入。\nimport { useDialog, useMessage } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport { translateError } from '#/utils/error'\n\nexport interface ConfirmOptions<T> {\n content: string\n /** 默认 t('common.confirmTitle') */\n title?: string\n type?: 'warning' | 'error' | 'info'\n action: () => Promise<T>\n /** false = 成功不弹 toast(后续另有提示时) */\n successMsg?: string | false\n}\n\nexport function useConfirm() {\n const dialog = useDialog()\n const message = useMessage()\n const { t } = useI18n()\n\n /** 仅确认不执行:取消/关闭/遮罩/Esc 均 resolve(false)。Esc 必须显式接 onEsc——Naive 的 Esc 路径不走 onClose。 */\n function ask(opts: {\n content: string\n title?: string\n type?: 'warning' | 'error' | 'info'\n }): Promise<boolean> {\n return new Promise(resolve => {\n dialog.create({\n type: opts.type ?? 'warning',\n title: opts.title ?? t('common.confirmTitle'),\n content: opts.content,\n positiveText: t('common.confirm'),\n negativeText: t('common.cancel'),\n onPositiveClick: () => resolve(true),\n onNegativeClick: () => resolve(false),\n onClose: () => resolve(false),\n onMaskClick: () => resolve(false),\n onEsc: () => resolve(false),\n })\n })\n }\n\n /** 执行 → 成/败 toast。resolve(true) 仅当执行成功。 */\n async function run<T>(action: () => Promise<T>, successMsg?: string | false): Promise<boolean> {\n try {\n await action()\n if (successMsg !== false) message.success(successMsg ?? t('common.success'))\n return true\n } catch (e) {\n message.error(translateError(e))\n return false\n }\n }\n\n /** dialog 确认 → run(action 在 dialog 挂起期间执行)。取消/关闭/Esc/失败均 resolve(false)。 */\n function confirm<T>(opts: ConfirmOptions<T>): Promise<boolean> {\n return new Promise(resolve => {\n // Naive 只把关闭延迟到 onPositiveClick 的 Promise settle,不会自动置 loading/锁按钮——\n // 手动锁死全部关闭路径,防连点重复执行、防执行中取消导致「已删成功但表格没刷新」。\n let busy = false\n const inst = dialog.create({\n type: opts.type ?? 'warning',\n title: opts.title ?? t('common.confirmTitle'),\n content: opts.content,\n positiveText: t('common.confirm'),\n negativeText: t('common.cancel'),\n onPositiveClick: () => {\n busy = true\n inst.loading = true // 确认钮 loading + 禁点\n inst.closeOnEsc = false\n inst.maskClosable = false\n return run(opts.action, opts.successMsg).then(resolve)\n },\n onNegativeClick: () => {\n if (busy) return false // 执行中禁取消\n resolve(false)\n },\n onClose: () => {\n if (busy) return false\n resolve(false)\n },\n onMaskClick: () => {\n if (busy) return false\n resolve(false)\n },\n onEsc: () => resolve(false), // busy 时已由 closeOnEsc=false 挡住,不会走到这里\n })\n })\n }\n\n return { ask, confirm, run }\n}\n"],"names":["useConfirm","dialog","useDialog","message","useMessage","useI18n","ask","opts","resolve","run","action","successMsg","e","translateError","confirm","busy","inst"],"mappings":";;;AAoBO,SAASA,IAAa;AAC3B,QAAMC,IAASC,EAAA,GACTC,IAAUC,EAAA,GACV,EAAE,EAAA,IAAMC,EAAA;AAGd,WAASC,EAAIC,GAIQ;AACnB,WAAO,IAAI,QAAQ,CAAAC,MAAW;AAC5B,MAAAP,EAAO,OAAO;AAAA,QACZ,MAAMM,EAAK,QAAQ;AAAA,QACnB,OAAOA,EAAK,SAAS,EAAE,qBAAqB;AAAA,QAC5C,SAASA,EAAK;AAAA,QACd,cAAc,EAAE,gBAAgB;AAAA,QAChC,cAAc,EAAE,eAAe;AAAA,QAC/B,iBAAiB,MAAMC,EAAQ,EAAI;AAAA,QACnC,iBAAiB,MAAMA,EAAQ,EAAK;AAAA,QACpC,SAAS,MAAMA,EAAQ,EAAK;AAAA,QAC5B,aAAa,MAAMA,EAAQ,EAAK;AAAA,QAChC,OAAO,MAAMA,EAAQ,EAAK;AAAA,MAAA,CAC3B;AAAA,IACH,CAAC;AAAA,EACH;AAGA,iBAAeC,EAAOC,GAA0BC,GAA+C;AAC7F,QAAI;AACF,mBAAMD,EAAA,GACFC,MAAe,MAAOR,EAAQ,QAAQQ,KAAc,EAAE,gBAAgB,CAAC,GACpE;AAAA,IACT,SAASC,GAAG;AACV,aAAAT,EAAQ,MAAMU,EAAeD,CAAC,CAAC,GACxB;AAAA,IACT;AAAA,EACF;AAGA,WAASE,EAAWP,GAA2C;AAC7D,WAAO,IAAI,QAAQ,CAAAC,MAAW;AAG5B,UAAIO,IAAO;AACX,YAAMC,IAAOf,EAAO,OAAO;AAAA,QACzB,MAAMM,EAAK,QAAQ;AAAA,QACnB,OAAOA,EAAK,SAAS,EAAE,qBAAqB;AAAA,QAC5C,SAASA,EAAK;AAAA,QACd,cAAc,EAAE,gBAAgB;AAAA,QAChC,cAAc,EAAE,eAAe;AAAA,QAC/B,iBAAiB,OACfQ,IAAO,IACPC,EAAK,UAAU,IACfA,EAAK,aAAa,IAClBA,EAAK,eAAe,IACbP,EAAIF,EAAK,QAAQA,EAAK,UAAU,EAAE,KAAKC,CAAO;AAAA,QAEvD,iBAAiB,MAAM;AACrB,cAAIO,EAAM,QAAO;AACjB,UAAAP,EAAQ,EAAK;AAAA,QACf;AAAA,QACA,SAAS,MAAM;AACb,cAAIO,EAAM,QAAO;AACjB,UAAAP,EAAQ,EAAK;AAAA,QACf;AAAA,QACA,aAAa,MAAM;AACjB,cAAIO,EAAM,QAAO;AACjB,UAAAP,EAAQ,EAAK;AAAA,QACf;AAAA,QACA,OAAO,MAAMA,EAAQ,EAAK;AAAA;AAAA,MAAA,CAC3B;AAAA,IACH,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,KAAAF,GAAK,SAAAQ,GAAS,KAAAL,EAAA;AACzB;"}
1
+ {"version":3,"file":"useConfirm.js","sources":["../../src/composables/useConfirm.ts"],"sourcesContent":["// 二次确认 + 结果 toast 的样板收敛:\n// ask —— 仅弹确认框不执行动作,resolve(用户是否确认);给需要自管后续流程的组件(如 StatusSwitch)。\n// confirm —— dialog 确认后执行 action,成/败统一 toast,resolve(执行是否成功);给不适合内联的重操作。\n// run —— 不弹 dialog 的后半段「执行 → toast」,配模板层 n-popconfirm 的 positive-click 用\n// (popconfirm 是触发器旁的内联组件,保留在模板层比 composable 化更顺手)。\n// 仅限 setup 中调用:useDialog/useMessage 依赖 App.vue 的 Provider 注入。\nimport { useDialog, useMessage } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport { translateError } from '#/utils/error'\n\nexport interface ConfirmOptions<T> {\n content: string\n /** 默认 t('common.confirmTitle') */\n title?: string\n type?: 'warning' | 'error' | 'info'\n action: () => Promise<T>\n /** false = 成功不弹 toast(后续另有提示时) */\n successMsg?: string | false\n}\n\nexport function useConfirm() {\n const dialog = useDialog()\n const message = useMessage()\n const { t } = useI18n()\n\n /** 仅确认不执行:取消/关闭/遮罩/Esc 均 resolve(false)。Esc 必须显式接 onEsc——Naive 的 Esc 路径不走 onClose。 */\n function ask(opts: {\n content: string\n title?: string\n type?: 'warning' | 'error' | 'info'\n }): Promise<boolean> {\n return new Promise(resolve => {\n dialog.create({\n type: opts.type ?? 'warning',\n title: opts.title ?? t('common.confirmTitle'),\n content: opts.content,\n positiveText: t('common.confirm'),\n negativeText: t('common.cancel'),\n onPositiveClick: () => resolve(true),\n onNegativeClick: () => resolve(false),\n onClose: () => resolve(false),\n onMaskClick: () => resolve(false),\n onEsc: () => resolve(false),\n })\n })\n }\n\n /** 执行 → 成/败 toast。resolve(true) 仅当执行成功。 */\n async function run<T>(action: () => Promise<T>, successMsg?: string | false): Promise<boolean> {\n try {\n await action()\n if (successMsg !== false) message.success(successMsg ?? t('common.success'))\n return true\n } catch (e) {\n message.error(translateError(e))\n return false\n }\n }\n\n /** dialog 确认 → run(action 在 dialog 挂起期间执行)。取消/关闭/Esc/失败均 resolve(false)。 */\n function confirm<T>(opts: ConfirmOptions<T>): Promise<boolean> {\n return new Promise(resolve => {\n // Naive 只把关闭延迟到 onPositiveClick 的 Promise settle,不会自动置 loading/锁按钮——\n // 手动锁死全部关闭路径,防连点重复执行、防执行中取消导致「已删成功但表格没刷新」。\n let busy = false\n const inst = dialog.create({\n type: opts.type ?? 'warning',\n title: opts.title ?? t('common.confirmTitle'),\n content: opts.content,\n positiveText: t('common.confirm'),\n negativeText: t('common.cancel'),\n onPositiveClick: () => {\n busy = true\n inst.loading = true // 确认钮 loading + 禁点\n inst.closeOnEsc = false\n inst.maskClosable = false\n return run(opts.action, opts.successMsg).then(resolve)\n },\n onNegativeClick: () => {\n if (busy) return false // 执行中禁取消\n resolve(false)\n },\n onClose: () => {\n if (busy) return false\n resolve(false)\n },\n onMaskClick: () => {\n if (busy) return false\n resolve(false)\n },\n onEsc: () => resolve(false), // busy 时已由 closeOnEsc=false 挡住,不会走到这里\n })\n })\n }\n\n return { ask, confirm, run }\n}\n"],"names":["useConfirm","dialog","useDialog","message","useMessage","useI18n","ask","opts","resolve","run","action","successMsg","e","translateError","confirm","busy","inst"],"mappings":";;;AAoBO,SAASA,IAAa;AAC3B,QAAMC,IAASC,EAAA,GACTC,IAAUC,EAAA,GACV,EAAE,EAAA,IAAMC,EAAA;AAGd,WAASC,EAAIC,GAIQ;AACnB,WAAO,IAAI,QAAQ,CAAAC,MAAW;AAC5B,MAAAP,EAAO,OAAO;AAAA,QACZ,MAAMM,EAAK,QAAQ;AAAA,QACnB,OAAOA,EAAK,SAAS,EAAE,qBAAqB;AAAA,QAC5C,SAASA,EAAK;AAAA,QACd,cAAc,EAAE,gBAAgB;AAAA,QAChC,cAAc,EAAE,eAAe;AAAA,QAC/B,iBAAiB,MAAMC,EAAQ,EAAI;AAAA,QACnC,iBAAiB,MAAMA,EAAQ,EAAK;AAAA,QACpC,SAAS,MAAMA,EAAQ,EAAK;AAAA,QAC5B,aAAa,MAAMA,EAAQ,EAAK;AAAA,QAChC,OAAO,MAAMA,EAAQ,EAAK;AAAA,MAAA,CAC3B;AAAA,IACH,CAAC;AAAA,EACH;AAGA,iBAAeC,EAAOC,GAA0BC,GAA+C;AAC7F,QAAI;AACF,mBAAMD,EAAA,GACFC,MAAe,MAAOR,EAAQ,QAAQQ,KAAc,EAAE,gBAAgB,CAAC,GACpE;AAAA,IACT,SAASC,GAAG;AACV,aAAAT,EAAQ,MAAMU,EAAeD,CAAC,CAAC,GACxB;AAAA,IACT;AAAA,EACF;AAGA,WAASE,EAAWP,GAA2C;AAC7D,WAAO,IAAI,QAAQ,CAAAC,MAAW;AAG5B,UAAIO,IAAO;AACX,YAAMC,IAAOf,EAAO,OAAO;AAAA,QACzB,MAAMM,EAAK,QAAQ;AAAA,QACnB,OAAOA,EAAK,SAAS,EAAE,qBAAqB;AAAA,QAC5C,SAASA,EAAK;AAAA,QACd,cAAc,EAAE,gBAAgB;AAAA,QAChC,cAAc,EAAE,eAAe;AAAA,QAC/B,iBAAiB,OACfQ,IAAO,IACPC,EAAK,UAAU,IACfA,EAAK,aAAa,IAClBA,EAAK,eAAe,IACbP,EAAIF,EAAK,QAAQA,EAAK,UAAU,EAAE,KAAKC,CAAO;AAAA,QAEvD,iBAAiB,MAAM;AACrB,cAAIO,EAAM,QAAO;AACjB,UAAAP,EAAQ,EAAK;AAAA,QACf;AAAA,QACA,SAAS,MAAM;AACb,cAAIO,EAAM,QAAO;AACjB,UAAAP,EAAQ,EAAK;AAAA,QACf;AAAA,QACA,aAAa,MAAM;AACjB,cAAIO,EAAM,QAAO;AACjB,UAAAP,EAAQ,EAAK;AAAA,QACf;AAAA,QACA,OAAO,MAAMA,EAAQ,EAAK;AAAA;AAAA,MAAA,CAC3B;AAAA,IACH,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,KAAAF,GAAK,SAAAQ,GAAS,KAAAL,EAAA;AACzB;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useLayoutMenu.js","sources":["../../src/composables/useLayoutMenu.ts"],"sourcesContent":["// auth.menuTree → n-menu 的 MenuOption,外加混合布局下顶栏一级 / 侧栏二级的选中态联动。\n// 用法:default.vue 与 AppHeader 各自 useLayoutMenu(),createSharedComposable 保证它们共享同一份选中态。\n// 坑:off-menu 路由(如 /personal/*)命中不到任何一级,此时保留上一次的一级选中,别把顶栏清空。\nimport { computed, h, ref, watch } from 'vue'\nimport { NBadge, type MenuOption } from 'naive-ui'\nimport { createSharedComposable } from '@vueuse/core'\nimport AppIcon from '#/components/AppIcon.vue'\nimport { menuBadge } from '#/composables/useMenuBadge'\nimport { router } from '#/router'\nimport { useAuthStore } from '#/stores/auth'\nimport { useAppStore } from '#/stores/app'\nimport { translateMenuTitle } from '#/locales/menuTitle'\nimport { isFullscreenPath, isHttpUrl } from '#/utils/url'\nimport { MenuType, type MenuNode } from '#/types/menu'\n\n/**\n * 新标签页打开的两类 key → window.open;返回是否已处理。\n * - 外链菜单:path 为 http(s) URL。\n * - 站内整屏页:path 以 `/fullscreen/` 开头(大屏看板),站内静态路由,但必须脱离布局壳整屏显示。\n */\nfunction openIfExternal(key: string): boolean {\n if (!isHttpUrl(key) && !isFullscreenPath(key)) return false\n window.open(key, '_blank', 'noopener,noreferrer')\n return true\n}\n\n// 无图标兜底:rail/折叠态的 n-menu 只画图标,缺图标会渲染成空槽“消失”(§组织管理),故永不返回 undefined。\n// 走 AppIcon 而非裸 Icon:菜单图标是库里配的,子集外的名字要由它懒加载整集兜底,否则渲染不出来。\nfunction renderIcon(name: string | undefined, fallback = 'ph:dot-outline-duotone') {\n return () => h(AppIcon, { icon: name || fallback, size: 18 })\n}\n\n// 角标(registerMenuBadge 登记的未读数/待办数)→ n-menu 的 extra 槽。\n// 读值在渲染函数里做,故登记的 ref 一变角标就变,不必重算整棵 options。\n// 折叠 / rail 态 n-menu 只画图标、不渲染 extra,角标随之隐藏——这是 n-menu 的行为,不另做补偿。\nfunction renderBadge(path: string | undefined) {\n if (!path) return undefined\n return () => {\n const value = menuBadge(path)\n if (value === null || value === undefined || value === 0 || value === '') return null\n return h(NBadge, { value, max: 99 })\n }\n}\n\n// 菜单树 → n-menu options:剥按钮、丢空目录、页面叶子 key=路由 path。目录/叶子各自兜底图标。\nfunction toOptions(nodes: MenuNode[]): MenuOption[] {\n return nodes\n .toSorted((a, b) => a.sort - b.sort)\n .filter(n => n.type !== MenuType.Button && n.visible !== false)\n .map<MenuOption | null>(n => {\n if (n.type === MenuType.Catalog) {\n const children = toOptions(n.children ?? [])\n if (!children.length) return null\n return {\n label: translateMenuTitle(n.title, n.path),\n key: `cat-${n.id}`,\n icon: renderIcon(n.icon, 'ph:folder-duotone'),\n children,\n }\n }\n return {\n label: translateMenuTitle(n.title, n.path),\n key: n.path ?? `menu-${n.id}`,\n icon: renderIcon(n.icon),\n extra: renderBadge(n.path),\n }\n })\n .filter((o): o is MenuOption => o !== null)\n}\n\nfunction containsKey(opt: MenuOption, path: string): boolean {\n if (opt.key === path) return true\n return ((opt.children as MenuOption[]) ?? []).some(c => containsKey(c, path))\n}\nfunction firstLeafKey(opt: MenuOption): string | undefined {\n if (typeof opt.key === 'string' && opt.key.startsWith('/')) return opt.key\n for (const c of (opt.children as MenuOption[]) ?? []) {\n const k = firstLeafKey(c)\n if (k) return k\n }\n return undefined\n}\n\n/**\n * 布局菜单派生 + 一/二级选中态联动(混合布局用)。\n * createSharedComposable → default.vue 与 AppHeader 共享同一份 selectedL1,不各自漂移。\n * 走 router.currentRoute(全局 ref),不依赖组件上下文的 useRoute。\n */\nfunction useLayoutMenuImpl() {\n const auth = useAuthStore()\n const app = useAppStore()\n\n // 工作台是每个应用菜单树里的一条(Sort=0 → 排第一项),跟着应用变,不用手工 prepend。\n const menuOptions = computed<MenuOption[]>(() => {\n void app.locale // 依赖:切换语言时 i18n key 标题重算\n return toOptions(auth.menuTree)\n })\n\n // 一级(剥 children → n-menu 渲染为可选普通项,无展开箭头)。\n const l1Options = computed<MenuOption[]>(() =>\n menuOptions.value.map(o => ({ label: o.label, key: o.key, icon: o.icon, extra: o.extra })),\n )\n\n const activeKey = computed(() => router.currentRoute.value.path)\n const selectedL1 = ref<string>()\n\n function findActiveL1(path: string): string | undefined {\n const top = menuOptions.value.find(o => containsKey(o, path))\n return top ? String(top.key) : undefined\n }\n\n // 路由 → 一级同步:命中才覆写(off-menu 路由如 /personal/* 保留上次);首次 seed 头一项。\n watch(\n () => router.currentRoute.value.path,\n path => {\n const l1 = findActiveL1(path)\n if (l1) selectedL1.value = l1\n else if (selectedL1.value === undefined)\n selectedL1.value = menuOptions.value[0]?.key as string | undefined\n },\n { immediate: true },\n )\n // 菜单可能晚于首个 watch 到达(F5 重建):补 seed / 修正失效选择。\n watch(menuOptions, opts => {\n if (selectedL1.value === undefined || !opts.some(o => o.key === selectedL1.value)) {\n selectedL1.value = findActiveL1(activeKey.value) ?? (opts[0]?.key as string | undefined)\n }\n })\n\n const l2Options = computed<MenuOption[]>(() => {\n const top = menuOptions.value.find(o => o.key === selectedL1.value)\n return (top?.children as MenuOption[]) ?? []\n })\n\n function onSelect(key: string) {\n if (openIfExternal(key)) return\n if (key.startsWith('/')) router.push(key)\n }\n function onSelectL1(key: string) {\n if (openIfExternal(key)) return\n selectedL1.value = key // 先设,L2 列立即刷新,不等导航\n const top = menuOptions.value.find(o => o.key === key)\n const target = key.startsWith('/') ? key : top ? firstLeafKey(top) : undefined\n if (target) router.push(target)\n }\n\n return { menuOptions, l1Options, l2Options, selectedL1, activeKey, onSelect, onSelectL1 }\n}\n\nexport const useLayoutMenu = createSharedComposable(useLayoutMenuImpl)\n"],"names":["openIfExternal","key","isHttpUrl","isFullscreenPath","renderIcon","name","fallback","AppIcon","renderBadge","path","value","menuBadge","NBadge","toOptions","nodes","a","b","n","MenuType","children","translateMenuTitle","o","containsKey","opt","c","firstLeafKey","k","useLayoutMenuImpl","auth","useAuthStore","app","useAppStore","menuOptions","computed","l1Options","activeKey","router","selectedL1","ref","findActiveL1","top","watch","l1","_a","opts","l2Options","onSelect","onSelectL1","target","useLayoutMenu","createSharedComposable"],"mappings":"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"}
1
+ {"version":3,"file":"useLayoutMenu.js","sources":["../../src/composables/useLayoutMenu.ts"],"sourcesContent":["// auth.menuTree → n-menu 的 MenuOption,外加混合布局下顶栏一级 / 侧栏二级的选中态联动。\n// 用法:default.vue 与 AppHeader 各自 useLayoutMenu(),createSharedComposable 保证它们共享同一份选中态。\n// 坑:off-menu 路由(如 /personal/*)命中不到任何一级,此时保留上一次的一级选中,别把顶栏清空。\nimport { computed, h, ref, watch } from 'vue'\nimport { NBadge, type MenuOption } from 'naive-ui'\nimport { createSharedComposable } from '@vueuse/core'\nimport AppIcon from '#/components/AppIcon.vue'\nimport { menuBadge } from '#/composables/useMenuBadge'\nimport { router } from '#/router'\nimport { useAuthStore } from '#/stores/auth'\nimport { useAppStore } from '#/stores/app'\nimport { translateMenuTitle } from '#/locales/menuTitle'\nimport { isFullscreenPath, isHttpUrl } from '#/utils/url'\nimport { MenuType, type MenuNode } from '#/types/menu'\n\n/**\n * 新标签页打开的两类 key → window.open;返回是否已处理。\n * - 外链菜单:path 为 http(s) URL。\n * - 站内整屏页:path 以 `/fullscreen/` 开头(大屏看板),站内静态路由,但必须脱离布局壳整屏显示。\n */\nfunction openIfExternal(key: string): boolean {\n if (!isHttpUrl(key) && !isFullscreenPath(key)) return false\n window.open(key, '_blank', 'noopener,noreferrer')\n return true\n}\n\n// 无图标兜底:rail/折叠态的 n-menu 只画图标,缺图标会渲染成空槽、整项像“消失”了,故永不返回 undefined。\n// 走 AppIcon 而非裸 Icon:菜单图标是库里配的,子集外的名字要由它懒加载整集兜底,否则渲染不出来。\nfunction renderIcon(name: string | undefined, fallback = 'ph:dot-outline-duotone') {\n return () => h(AppIcon, { icon: name || fallback, size: 18 })\n}\n\n// 角标(registerMenuBadge 登记的未读数/待办数)→ n-menu 的 extra 槽。\n// 读值在渲染函数里做,故登记的 ref 一变角标就变,不必重算整棵 options。\n// 折叠 / rail 态 n-menu 只画图标、不渲染 extra,角标随之隐藏——这是 n-menu 的行为,不另做补偿。\nfunction renderBadge(path: string | undefined) {\n if (!path) return undefined\n return () => {\n const value = menuBadge(path)\n if (value === null || value === undefined || value === 0 || value === '') return null\n return h(NBadge, { value, max: 99 })\n }\n}\n\n// 菜单树 → n-menu options:剥按钮、丢空目录、页面叶子 key=路由 path。目录/叶子各自兜底图标。\nfunction toOptions(nodes: MenuNode[]): MenuOption[] {\n return nodes\n .toSorted((a, b) => a.sort - b.sort)\n .filter(n => n.type !== MenuType.Button && n.visible !== false)\n .map<MenuOption | null>(n => {\n if (n.type === MenuType.Catalog) {\n const children = toOptions(n.children ?? [])\n if (!children.length) return null\n return {\n label: translateMenuTitle(n.title, n.path),\n key: `cat-${n.id}`,\n icon: renderIcon(n.icon, 'ph:folder-duotone'),\n children,\n }\n }\n return {\n label: translateMenuTitle(n.title, n.path),\n key: n.path ?? `menu-${n.id}`,\n icon: renderIcon(n.icon),\n extra: renderBadge(n.path),\n }\n })\n .filter((o): o is MenuOption => o !== null)\n}\n\nfunction containsKey(opt: MenuOption, path: string): boolean {\n if (opt.key === path) return true\n return ((opt.children as MenuOption[]) ?? []).some(c => containsKey(c, path))\n}\nfunction firstLeafKey(opt: MenuOption): string | undefined {\n if (typeof opt.key === 'string' && opt.key.startsWith('/')) return opt.key\n for (const c of (opt.children as MenuOption[]) ?? []) {\n const k = firstLeafKey(c)\n if (k) return k\n }\n return undefined\n}\n\n/**\n * 布局菜单派生 + 一/二级选中态联动(混合布局用)。\n * createSharedComposable → default.vue 与 AppHeader 共享同一份 selectedL1,不各自漂移。\n * 走 router.currentRoute(全局 ref),不依赖组件上下文的 useRoute。\n */\nfunction useLayoutMenuImpl() {\n const auth = useAuthStore()\n const app = useAppStore()\n\n // 工作台是每个应用菜单树里的一条(Sort=0 → 排第一项),跟着应用变,不用手工 prepend。\n const menuOptions = computed<MenuOption[]>(() => {\n void app.locale // 依赖:切换语言时 i18n key 标题重算\n return toOptions(auth.menuTree)\n })\n\n // 一级(剥 children → n-menu 渲染为可选普通项,无展开箭头)。\n const l1Options = computed<MenuOption[]>(() =>\n menuOptions.value.map(o => ({ label: o.label, key: o.key, icon: o.icon, extra: o.extra })),\n )\n\n const activeKey = computed(() => router.currentRoute.value.path)\n const selectedL1 = ref<string>()\n\n function findActiveL1(path: string): string | undefined {\n const top = menuOptions.value.find(o => containsKey(o, path))\n return top ? String(top.key) : undefined\n }\n\n // 路由 → 一级同步:命中才覆写(off-menu 路由如 /personal/* 保留上次);首次 seed 头一项。\n watch(\n () => router.currentRoute.value.path,\n path => {\n const l1 = findActiveL1(path)\n if (l1) selectedL1.value = l1\n else if (selectedL1.value === undefined)\n selectedL1.value = menuOptions.value[0]?.key as string | undefined\n },\n { immediate: true },\n )\n // 菜单可能晚于首个 watch 到达(F5 重建):补 seed / 修正失效选择。\n watch(menuOptions, opts => {\n if (selectedL1.value === undefined || !opts.some(o => o.key === selectedL1.value)) {\n selectedL1.value = findActiveL1(activeKey.value) ?? (opts[0]?.key as string | undefined)\n }\n })\n\n const l2Options = computed<MenuOption[]>(() => {\n const top = menuOptions.value.find(o => o.key === selectedL1.value)\n return (top?.children as MenuOption[]) ?? []\n })\n\n function onSelect(key: string) {\n if (openIfExternal(key)) return\n if (key.startsWith('/')) router.push(key)\n }\n function onSelectL1(key: string) {\n if (openIfExternal(key)) return\n selectedL1.value = key // 先设,L2 列立即刷新,不等导航\n const top = menuOptions.value.find(o => o.key === key)\n const target = key.startsWith('/') ? key : top ? firstLeafKey(top) : undefined\n if (target) router.push(target)\n }\n\n return { menuOptions, l1Options, l2Options, selectedL1, activeKey, onSelect, onSelectL1 }\n}\n\nexport const useLayoutMenu = createSharedComposable(useLayoutMenuImpl)\n"],"names":["openIfExternal","key","isHttpUrl","isFullscreenPath","renderIcon","name","fallback","AppIcon","renderBadge","path","value","menuBadge","NBadge","toOptions","nodes","a","b","n","MenuType","children","translateMenuTitle","o","containsKey","opt","c","firstLeafKey","k","useLayoutMenuImpl","auth","useAuthStore","app","useAppStore","menuOptions","computed","l1Options","activeKey","router","selectedL1","ref","findActiveL1","top","watch","l1","_a","opts","l2Options","onSelect","onSelectL1","target","useLayoutMenu","createSharedComposable"],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  * 解耦长连接与铃铛组件(二者互不 import 对方)。
4
4
  */
5
5
  export declare const noticeBus: import('@vueuse/core').UseEventBusReturn<void, any>;
6
- /** 额外 hub 事件的处理器签名:后端 NotifyUserAsync / BroadcastAsync 恒发单个 data 载荷。 */
6
+ /** 额外 hub 事件的处理器签名:后端 NotifyUserAsync / NotifyAllAsync 恒发单个 data 载荷。 */
7
7
  export type RealtimeHandler = (data: unknown) => void;
8
8
  /**
9
9
  * 在内置这条 SignalR 连接上登记自己的 hub 事件,返回退订函数。
@@ -1 +1 @@
1
- {"version":3,"file":"useRealtime.js","sources":["../../src/composables/useRealtime.ts"],"sourcesContent":["import { useRouter } from 'vue-router'\nimport { useMessage } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport { useEventBus } from '@vueuse/core'\nimport { HubConnectionBuilder, LogLevel, type HubConnection } from '@microsoft/signalr'\nimport { useUserStore } from '#/stores/user'\nimport { runtime } from '#/lib/runtime'\n\n/**\n * 未读通知刷新总线:useRealtime 收到后端 `notice-changed` 推送后 emit,NoticeBell 订阅后立即重拉未读角标。\n * 解耦长连接与铃铛组件(二者互不 import 对方)。\n */\nexport const noticeBus = useEventBus<void>('notice-changed')\n\n// 进程内单例连接:鉴权外壳(default.vue)挂载一次即建一条;重复 start 幂等(已连不再建)。\nlet connection: HubConnection | null = null\n// 主动登出/改密重登:后端 Logout 也走 RevokeAsync → 推 force-logout;本页应静默,勿弹「您已被强制下线」。\n// 其它同会话标签页仍会收到推送并提示(它们没调 beginVoluntaryLogout)。下次 start 清零。\nlet voluntaryLogout = false\n\n/** 额外 hub 事件的处理器签名:后端 NotifyUserAsync / BroadcastAsync 恒发单个 data 载荷。 */\nexport type RealtimeHandler = (data: unknown) => void\n\n// 扩展模块登记的 hub 事件。必须能挂到内置这条连接上:另建一条连向同一个 /hub/realtime 的连接,\n// 意味着一个在线用户两条 WebSocket,而后端是按 userId 群发到该用户的全部连接 ——\n// notice-changed / force-logout 这类内置事件会被投递两遍。\nconst extraHandlers = new Map<string, Set<RealtimeHandler>>()\n// 当前连接上已 conn.on 过的事件名:每建一条新连接清空,保证既不重复绑定也不漏绑。\nlet boundEvents = new Set<string>()\n\nfunction bindExtra(conn: HubConnection, event: string): void {\n if (boundEvents.has(event)) return\n boundEvents.add(event)\n // 一个事件只挂一个派发器,退订只从 Set 里摘处理器,不调 conn.off(event) ——\n // 那会把同事件上其它模块的处理器一起摘掉。\n conn.on(event, (data: unknown) => {\n for (const handler of extraHandlers.get(event) ?? []) handler(data)\n })\n}\n\n/**\n * 在内置这条 SignalR 连接上登记自己的 hub 事件,返回退订函数。\n * <p>建连之前登记的于 start() 时统一绑定,之后登记的直接挂到现有连接;连接重建(登出后再登录)会自动重绑。\n * 组件里用记得在 onScopeDispose / onUnmounted 调返回值退订,否则热更新与路由切换会越挂越多。</p>\n */\nexport function onRealtime(event: string, handler: RealtimeHandler): () => void {\n const handlers = extraHandlers.get(event) ?? new Set<RealtimeHandler>()\n handlers.add(handler)\n extraHandlers.set(event, handlers)\n if (connection) bindExtra(connection, event)\n return () => {\n handlers.delete(handler)\n // 认一下手里这个 Set 还是不是表里那个:退订被调两次时,中间可能已有人重新登记同名事件,\n // 照删会把后来者的处理器一起摘掉,而且是静默的。\n if (handlers.size === 0 && extraHandlers.get(event) === handlers) extraHandlers.delete(event)\n }\n}\n\nasync function stopConnection(): Promise<void> {\n const c = connection\n connection = null\n if (c) {\n try {\n await c.stop()\n } catch {\n /* 停止失败无害 */\n }\n }\n}\n\n/**\n * 主动登出前调用:标记自愿退出 + 先断 SignalR。\n * <p>后端会话吊销会推 `force-logout`(与管理员强退共用通道);本页断连后收不到,即便迟到推送也不弹强制下线文案。\n * 其它标签页仍可被踢并提示。</p>\n */\nexport async function beginVoluntaryLogout(): Promise<void> {\n voluntaryLogout = true\n await stopConnection()\n}\n\n/**\n * 实时通知客户端(SignalR)。仅在鉴权外壳挂载时 start、登出/卸载时 stop。\n * <p>后端 `SmartAdmin:Realtime:Enabled` 关闭时 Hub 不存在 → 初次连接失败,**静默退回** NoticeBell 的 30s 轮询兜底\n * 与「下次请求 401」惰性登出(纯增强,无回归)。令牌走 query `access_token`(浏览器 WebSocket 带不了 Authorization 头)。</p>\n * message/router/i18n 在 setup 期(default.vue)绑定,推送到达时复用,故务必在 setup 中调用本 composable。\n */\nexport function useRealtime() {\n const router = useRouter()\n const message = useMessage()\n const { t } = useI18n()\n\n function start() {\n if (connection || !useUserStore().accessToken) return\n voluntaryLogout = false\n const conn = new HubConnectionBuilder()\n .withUrl(`${runtime.apiBase}/hub/realtime`, {\n accessTokenFactory: () => useUserStore().accessToken,\n })\n .withAutomaticReconnect()\n .configureLogging(LogLevel.Warning)\n .build()\n\n // 强制下线:清会话 + 授权态 + 提示 + 跳登录(与 api/client.ts 刷新失败路径同款收尾)\n conn.on('force-logout', () => {\n const silent = voluntaryLogout\n voluntaryLogout = false\n void stopConnection()\n useUserStore().clear()\n void import('#/stores/auth').then(({ useAuthStore }) => useAuthStore().reset())\n void import('#/router').then(({ resetRouter }) => resetRouter())\n // 主动登出也会触发后端 force-logout 推送:只静默清会话,不谎报「被强制下线」。\n if (!silent) message.warning(t('realtime.forcedLogout'))\n if (router.currentRoute.value.path !== '/login') router.replace('/login')\n })\n // 公告变更:通知 NoticeBell 立即重拉未读(替代最长 30s 轮询延迟)\n conn.on('notice-changed', () => noticeBus.emit())\n // 扩展模块的事件:新连接从零绑一遍\n boundEvents = new Set()\n for (const event of extraHandlers.keys()) bindExtra(conn, event)\n\n connection = conn\n conn.start().catch(() => {\n // 初次连接失败(后端未开启实时 → Hub 404):静默退回轮询兜底,不重试刷屏。\n // withAutomaticReconnect 只在「连过又断」后重连,不重试初次 start,故这里不会造成对已关实时的后端反复叩门。\n if (connection === conn) connection = null\n })\n }\n\n return { start, stop: stopConnection }\n}\n"],"names":["noticeBus","useEventBus","connection","voluntaryLogout","extraHandlers","boundEvents","bindExtra","conn","event","data","handler","onRealtime","handlers","stopConnection","c","beginVoluntaryLogout","useRealtime","router","useRouter","message","useMessage","t","useI18n","start","useUserStore","HubConnectionBuilder","runtime","LogLevel","silent","useAuthStore","resetRouter"],"mappings":";;;;;;;AAYO,MAAMA,IAAYC,EAAkB,gBAAgB;AAG3D,IAAIC,IAAmC,MAGnCC,IAAkB;AAQtB,MAAMC,wBAAoB,IAAA;AAE1B,IAAIC,wBAAkB,IAAA;AAEtB,SAASC,EAAUC,GAAqBC,GAAqB;AAC3D,EAAIH,EAAY,IAAIG,CAAK,MACzBH,EAAY,IAAIG,CAAK,GAGrBD,EAAK,GAAGC,GAAO,CAACC,MAAkB;AAChC,eAAWC,KAAWN,EAAc,IAAII,CAAK,KAAK,CAAA,KAAYC,CAAI;AAAA,EACpE,CAAC;AACH;AAOO,SAASE,EAAWH,GAAeE,GAAsC;AAC9E,QAAME,IAAWR,EAAc,IAAII,CAAK,yBAAS,IAAA;AACjD,SAAAI,EAAS,IAAIF,CAAO,GACpBN,EAAc,IAAII,GAAOI,CAAQ,GAC7BV,KAAYI,EAAUJ,GAAYM,CAAK,GACpC,MAAM;AACX,IAAAI,EAAS,OAAOF,CAAO,GAGnBE,EAAS,SAAS,KAAKR,EAAc,IAAII,CAAK,MAAMI,KAAUR,EAAc,OAAOI,CAAK;AAAA,EAC9F;AACF;AAEA,eAAeK,IAAgC;AAC7C,QAAMC,IAAIZ;AAEV,MADAA,IAAa,MACTY;AACF,QAAI;AACF,YAAMA,EAAE,KAAA;AAAA,IACV,QAAQ;AAAA,IAER;AAEJ;AAOA,eAAsBC,IAAsC;AAC1D,EAAAZ,IAAkB,IAClB,MAAMU,EAAA;AACR;AAQO,SAASG,IAAc;AAC5B,QAAMC,IAASC,EAAA,GACTC,IAAUC,EAAA,GACV,EAAE,GAAAC,EAAA,IAAMC,EAAA;AAEd,WAASC,IAAQ;AACf,QAAIrB,KAAc,CAACsB,EAAA,EAAe,YAAa;AAC/C,IAAArB,IAAkB;AAClB,UAAMI,IAAO,IAAIkB,IACd,QAAQ,GAAGC,EAAQ,OAAO,iBAAiB;AAAA,MAC1C,oBAAoB,MAAMF,IAAe;AAAA,IAAA,CAC1C,EACA,uBAAA,EACA,iBAAiBG,EAAS,OAAO,EACjC,MAAA;AAGH,IAAApB,EAAK,GAAG,gBAAgB,MAAM;AAC5B,YAAMqB,IAASzB;AACf,MAAAA,IAAkB,IACbU,EAAA,GACLW,EAAA,EAAe,MAAA,GACV,OAAO,mBAAe,EAAE,KAAK,CAAC,EAAE,cAAAK,QAAmBA,IAAe,OAAO,GACzE,OAAO,oBAAU,EAAE,KAAK,CAAC,EAAE,aAAAC,EAAA,MAAkBA,GAAa,GAE1DF,KAAQT,EAAQ,QAAQE,EAAE,uBAAuB,CAAC,GACnDJ,EAAO,aAAa,MAAM,SAAS,YAAUA,EAAO,QAAQ,QAAQ;AAAA,IAC1E,CAAC,GAEDV,EAAK,GAAG,kBAAkB,MAAMP,EAAU,MAAM,GAEhDK,wBAAkB,IAAA;AAClB,eAAWG,KAASJ,EAAc,KAAA,EAAQ,CAAAE,EAAUC,GAAMC,CAAK;AAE/D,IAAAN,IAAaK,GACbA,EAAK,QAAQ,MAAM,MAAM;AAGvB,MAAIL,MAAeK,MAAML,IAAa;AAAA,IACxC,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,OAAAqB,GAAO,MAAMV,EAAA;AACxB;"}
1
+ {"version":3,"file":"useRealtime.js","sources":["../../src/composables/useRealtime.ts"],"sourcesContent":["import { useRouter } from 'vue-router'\nimport { useMessage } from 'naive-ui'\nimport { useI18n } from 'vue-i18n'\nimport { useEventBus } from '@vueuse/core'\nimport { HubConnectionBuilder, LogLevel, type HubConnection } from '@microsoft/signalr'\nimport { useUserStore } from '#/stores/user'\nimport { runtime } from '#/lib/runtime'\n\n/**\n * 未读通知刷新总线:useRealtime 收到后端 `notice-changed` 推送后 emit,NoticeBell 订阅后立即重拉未读角标。\n * 解耦长连接与铃铛组件(二者互不 import 对方)。\n */\nexport const noticeBus = useEventBus<void>('notice-changed')\n\n// 进程内单例连接:鉴权外壳(default.vue)挂载一次即建一条;重复 start 幂等(已连不再建)。\nlet connection: HubConnection | null = null\n// 主动登出/改密重登:后端 Logout 也走 RevokeAsync → 推 force-logout;本页应静默,勿弹「您已被强制下线」。\n// 其它同会话标签页仍会收到推送并提示(它们没调 beginVoluntaryLogout)。下次 start 清零。\nlet voluntaryLogout = false\n\n/** 额外 hub 事件的处理器签名:后端 NotifyUserAsync / NotifyAllAsync 恒发单个 data 载荷。 */\nexport type RealtimeHandler = (data: unknown) => void\n\n// 扩展模块登记的 hub 事件。必须能挂到内置这条连接上:另建一条连向同一个 /hub/realtime 的连接,\n// 意味着一个在线用户两条 WebSocket,而后端是按 userId 群发到该用户的全部连接 ——\n// notice-changed / force-logout 这类内置事件会被投递两遍。\nconst extraHandlers = new Map<string, Set<RealtimeHandler>>()\n// 当前连接上已 conn.on 过的事件名:每建一条新连接清空,保证既不重复绑定也不漏绑。\nlet boundEvents = new Set<string>()\n\nfunction bindExtra(conn: HubConnection, event: string): void {\n if (boundEvents.has(event)) return\n boundEvents.add(event)\n // 一个事件只挂一个派发器,退订只从 Set 里摘处理器,不调 conn.off(event) ——\n // 那会把同事件上其它模块的处理器一起摘掉。\n conn.on(event, (data: unknown) => {\n for (const handler of extraHandlers.get(event) ?? []) handler(data)\n })\n}\n\n/**\n * 在内置这条 SignalR 连接上登记自己的 hub 事件,返回退订函数。\n * <p>建连之前登记的于 start() 时统一绑定,之后登记的直接挂到现有连接;连接重建(登出后再登录)会自动重绑。\n * 组件里用记得在 onScopeDispose / onUnmounted 调返回值退订,否则热更新与路由切换会越挂越多。</p>\n */\nexport function onRealtime(event: string, handler: RealtimeHandler): () => void {\n const handlers = extraHandlers.get(event) ?? new Set<RealtimeHandler>()\n handlers.add(handler)\n extraHandlers.set(event, handlers)\n if (connection) bindExtra(connection, event)\n return () => {\n handlers.delete(handler)\n // 认一下手里这个 Set 还是不是表里那个:退订被调两次时,中间可能已有人重新登记同名事件,\n // 照删会把后来者的处理器一起摘掉,而且是静默的。\n if (handlers.size === 0 && extraHandlers.get(event) === handlers) extraHandlers.delete(event)\n }\n}\n\nasync function stopConnection(): Promise<void> {\n const c = connection\n connection = null\n if (c) {\n try {\n await c.stop()\n } catch {\n /* 停止失败无害 */\n }\n }\n}\n\n/**\n * 主动登出前调用:标记自愿退出 + 先断 SignalR。\n * <p>后端会话吊销会推 `force-logout`(与管理员强退共用通道);本页断连后收不到,即便迟到推送也不弹强制下线文案。\n * 其它标签页仍可被踢并提示。</p>\n */\nexport async function beginVoluntaryLogout(): Promise<void> {\n voluntaryLogout = true\n await stopConnection()\n}\n\n/**\n * 实时通知客户端(SignalR)。仅在鉴权外壳挂载时 start、登出/卸载时 stop。\n * <p>后端 `SmartAdmin:Realtime:Enabled` 关闭时 Hub 不存在 → 初次连接失败,**静默退回** NoticeBell 的 30s 轮询兜底\n * 与「下次请求 401」惰性登出(纯增强,无回归)。令牌走 query `access_token`(浏览器 WebSocket 带不了 Authorization 头)。</p>\n * message/router/i18n 在 setup 期(default.vue)绑定,推送到达时复用,故务必在 setup 中调用本 composable。\n */\nexport function useRealtime() {\n const router = useRouter()\n const message = useMessage()\n const { t } = useI18n()\n\n function start() {\n if (connection || !useUserStore().accessToken) return\n voluntaryLogout = false\n const conn = new HubConnectionBuilder()\n .withUrl(`${runtime.apiBase}/hub/realtime`, {\n accessTokenFactory: () => useUserStore().accessToken,\n })\n .withAutomaticReconnect()\n .configureLogging(LogLevel.Warning)\n .build()\n\n // 强制下线:清会话 + 授权态 + 提示 + 跳登录(与 api/client.ts 刷新失败路径同款收尾)\n conn.on('force-logout', () => {\n const silent = voluntaryLogout\n voluntaryLogout = false\n void stopConnection()\n useUserStore().clear()\n void import('#/stores/auth').then(({ useAuthStore }) => useAuthStore().reset())\n void import('#/router').then(({ resetRouter }) => resetRouter())\n // 主动登出也会触发后端 force-logout 推送:只静默清会话,不谎报「被强制下线」。\n if (!silent) message.warning(t('realtime.forcedLogout'))\n if (router.currentRoute.value.path !== '/login') router.replace('/login')\n })\n // 公告变更:通知 NoticeBell 立即重拉未读(替代最长 30s 轮询延迟)\n conn.on('notice-changed', () => noticeBus.emit())\n // 扩展模块的事件:新连接从零绑一遍\n boundEvents = new Set()\n for (const event of extraHandlers.keys()) bindExtra(conn, event)\n\n connection = conn\n conn.start().catch(() => {\n // 初次连接失败(后端未开启实时 → Hub 404):静默退回轮询兜底,不重试刷屏。\n // withAutomaticReconnect 只在「连过又断」后重连,不重试初次 start,故这里不会造成对已关实时的后端反复叩门。\n if (connection === conn) connection = null\n })\n }\n\n return { start, stop: stopConnection }\n}\n"],"names":["noticeBus","useEventBus","connection","voluntaryLogout","extraHandlers","boundEvents","bindExtra","conn","event","data","handler","onRealtime","handlers","stopConnection","c","beginVoluntaryLogout","useRealtime","router","useRouter","message","useMessage","t","useI18n","start","useUserStore","HubConnectionBuilder","runtime","LogLevel","silent","useAuthStore","resetRouter"],"mappings":";;;;;;;AAYO,MAAMA,IAAYC,EAAkB,gBAAgB;AAG3D,IAAIC,IAAmC,MAGnCC,IAAkB;AAQtB,MAAMC,wBAAoB,IAAA;AAE1B,IAAIC,wBAAkB,IAAA;AAEtB,SAASC,EAAUC,GAAqBC,GAAqB;AAC3D,EAAIH,EAAY,IAAIG,CAAK,MACzBH,EAAY,IAAIG,CAAK,GAGrBD,EAAK,GAAGC,GAAO,CAACC,MAAkB;AAChC,eAAWC,KAAWN,EAAc,IAAII,CAAK,KAAK,CAAA,KAAYC,CAAI;AAAA,EACpE,CAAC;AACH;AAOO,SAASE,EAAWH,GAAeE,GAAsC;AAC9E,QAAME,IAAWR,EAAc,IAAII,CAAK,yBAAS,IAAA;AACjD,SAAAI,EAAS,IAAIF,CAAO,GACpBN,EAAc,IAAII,GAAOI,CAAQ,GAC7BV,KAAYI,EAAUJ,GAAYM,CAAK,GACpC,MAAM;AACX,IAAAI,EAAS,OAAOF,CAAO,GAGnBE,EAAS,SAAS,KAAKR,EAAc,IAAII,CAAK,MAAMI,KAAUR,EAAc,OAAOI,CAAK;AAAA,EAC9F;AACF;AAEA,eAAeK,IAAgC;AAC7C,QAAMC,IAAIZ;AAEV,MADAA,IAAa,MACTY;AACF,QAAI;AACF,YAAMA,EAAE,KAAA;AAAA,IACV,QAAQ;AAAA,IAER;AAEJ;AAOA,eAAsBC,IAAsC;AAC1D,EAAAZ,IAAkB,IAClB,MAAMU,EAAA;AACR;AAQO,SAASG,IAAc;AAC5B,QAAMC,IAASC,EAAA,GACTC,IAAUC,EAAA,GACV,EAAE,GAAAC,EAAA,IAAMC,EAAA;AAEd,WAASC,IAAQ;AACf,QAAIrB,KAAc,CAACsB,EAAA,EAAe,YAAa;AAC/C,IAAArB,IAAkB;AAClB,UAAMI,IAAO,IAAIkB,IACd,QAAQ,GAAGC,EAAQ,OAAO,iBAAiB;AAAA,MAC1C,oBAAoB,MAAMF,IAAe;AAAA,IAAA,CAC1C,EACA,uBAAA,EACA,iBAAiBG,EAAS,OAAO,EACjC,MAAA;AAGH,IAAApB,EAAK,GAAG,gBAAgB,MAAM;AAC5B,YAAMqB,IAASzB;AACf,MAAAA,IAAkB,IACbU,EAAA,GACLW,EAAA,EAAe,MAAA,GACV,OAAO,mBAAe,EAAE,KAAK,CAAC,EAAE,cAAAK,QAAmBA,IAAe,OAAO,GACzE,OAAO,oBAAU,EAAE,KAAK,CAAC,EAAE,aAAAC,EAAA,MAAkBA,GAAa,GAE1DF,KAAQT,EAAQ,QAAQE,EAAE,uBAAuB,CAAC,GACnDJ,EAAO,aAAa,MAAM,SAAS,YAAUA,EAAO,QAAQ,QAAQ;AAAA,IAC1E,CAAC,GAEDV,EAAK,GAAG,kBAAkB,MAAMP,EAAU,MAAM,GAEhDK,wBAAkB,IAAA;AAClB,eAAWG,KAASJ,EAAc,KAAA,EAAQ,CAAAE,EAAUC,GAAMC,CAAK;AAE/D,IAAAN,IAAaK,GACbA,EAAK,QAAQ,MAAM,MAAM;AAGvB,MAAIL,MAAeK,MAAML,IAAa;AAAA,IACxC,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,OAAAqB,GAAO,MAAMV,EAAA;AACxB;"}
@@ -6,8 +6,8 @@ import { i18n, ExtModule } from './locales';
6
6
  import { ViewLoader } from './router/viewRegistry';
7
7
  import { BrandOptions } from './lib/runtime';
8
8
  /**
9
- * 一个插件(或应用自身)能往内核里注册的东西。每一项都是「消费方在自己的代码里写 import.meta.glob,
10
- * 把结果交进来」—— 内核是预编译的包,自己扫不到消费方的文件。
9
+ * 一个插件(或应用自身)能往内核里注册的东西。页面、语言包、图标这类文件集合由消费方在自己的代码里
10
+ * 写 import.meta.glob、把结果交进来 —— 内核是预编译的包,自己扫不到消费方的文件。
11
11
  */
12
12
  export interface SmartAdminPlugin {
13
13
  /** 页面表:`import.meta.glob('./views/**\/*.vue')`。键取 `/views/` 之后去掉 .vue(如 system/user/index),同名覆盖先注册者。 */
@@ -1,23 +1,24 @@
1
1
  import { createApp as g, computed as v } from "vue";
2
2
  import { createPinia as S } from "pinia";
3
3
  import h from "pinia-plugin-persistedstate";
4
- import { SMART_TABLE_DEFAULTS as x, createSmartTableDefaults as w } from "smart-naive-table";
5
- import y from "./App.vue.js";
4
+ import { SMART_TABLE_DEFAULTS as x, createSmartTableDefaults as T } from "smart-naive-table";
5
+ import w from "./App.vue.js";
6
6
  import { router as n } from "./router/index.js";
7
- import { registerLocales as A, i18n as i } from "./locales/index.js";
8
- import { registerMenuTitles as L } from "./locales/menuTitle.js";
9
- import { registerViews as T } from "./router/viewRegistry.js";
7
+ import { registerLocales as y, i18n as i } from "./locales/index.js";
8
+ import { registerMenuTitles as A } from "./locales/menuTitle.js";
9
+ import { registerViews as L } from "./router/viewRegistry.js";
10
10
  import { vAuth as E } from "./directives/auth.js";
11
11
  import { setupIcons as R } from "./lib/icons.js";
12
12
  import { reloadOnChunkError as u, reloadOnce as _ } from "./lib/chunkReload.js";
13
13
  import { runtime as o } from "./lib/runtime.js";
14
14
  import { useSite as D } from "./composables/useSite.js";
15
- function $(t = {}) {
16
- var m, c, f, d;
17
- o.apiBase = (t.apiBase ?? "").replace(/\/+$/, ""), t.version && (o.version = t.version), o.dev = !!t.dev, o.brand = t.brand ?? {}, (m = t.brand) != null && m.title && (D().site.title = t.brand.title);
15
+ import { trimTrailingSlashes as B } from "./utils/url.js";
16
+ function J(t = {}) {
17
+ var m, f, c, d;
18
+ o.apiBase = B(t.apiBase ?? ""), t.version && (o.version = t.version), o.dev = !!t.dev, o.brand = t.brand ?? {}, (m = t.brand) != null && m.title && (D().site.title = t.brand.title);
18
19
  const s = [...t.plugins ?? [], t], l = {};
19
20
  for (const e of s) {
20
- e.views && T(e.views), e.locales && A(e.locales), e.menuTitles && L(e.menuTitles), e.icons && Object.assign(l, e.icons);
21
+ e.views && L(e.views), e.locales && y(e.locales), e.menuTitles && A(e.menuTitles), e.icons && Object.assign(l, e.icons);
21
22
  for (const p of e.routes ?? []) n.addRoute(p);
22
23
  }
23
24
  R(l), window.addEventListener("unhandledrejection", (e) => {
@@ -27,19 +28,19 @@ function $(t = {}) {
27
28
  });
28
29
  const a = S();
29
30
  a.use(h);
30
- const r = g(y);
31
+ const r = g(w);
31
32
  r.use(a), r.use(n), r.use(i), r.directive("auth", E), r.config.errorHandler = (e, p, b) => {
32
33
  u(e) || console.error("[SmartAdmin] 未捕获异常", b, e);
33
34
  }, r.provide(
34
35
  x,
35
- w({
36
+ T({
36
37
  // 表格工具栏密度默认使用紧凑档;已有 storage-key 的用户选择仍按本地缓存优先。
37
- density: ((c = t.table) == null ? void 0 : c.density) ?? "compact",
38
+ density: ((f = t.table) == null ? void 0 : f.density) ?? "compact",
38
39
  // 内置兜底是 [10,20,50],后台列表动辄上千行,翻页翻不过来。
39
40
  // 上限 200:内核 Api:MaxPageSize 的默认值,选更大的后端会回 48001。
40
41
  // 注意 SmartTable 的**初始**每页条数走 default-page-size prop(不在这份可注入的 defaults 里),
41
42
  // 没传那个 prop 的页面初始仍是 10,条数选择器会显示一个不在列表里的值。
42
- pageSizes: ((f = t.table) == null ? void 0 : f.pageSizes) ?? [20, 50, 100, 200],
43
+ pageSizes: ((c = t.table) == null ? void 0 : c.pageSizes) ?? [20, 50, 100, 200],
43
44
  labels: v(() => {
44
45
  i.global.locale.value;
45
46
  const e = i.global.t;
@@ -71,6 +72,6 @@ function $(t = {}) {
71
72
  };
72
73
  }
73
74
  export {
74
- $ as createSmartAdmin
75
+ J as createSmartAdmin
75
76
  };
76
77
  //# sourceMappingURL=createSmartAdmin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createSmartAdmin.js","sources":["../src/createSmartAdmin.ts"],"sourcesContent":["import { computed, createApp, type App } from 'vue'\nimport { createPinia, type Pinia } from 'pinia'\nimport piniaPluginPersistedstate from 'pinia-plugin-persistedstate'\nimport type { RouteRecordRaw } from 'vue-router'\nimport { SMART_TABLE_DEFAULTS, createSmartTableDefaults } from 'smart-naive-table'\nimport AppRoot from '#/App.vue'\nimport { router } from '#/router'\nimport { i18n, registerLocales, type ExtModule } from '#/locales'\nimport { registerMenuTitles } from '#/locales/menuTitle'\nimport { registerViews, type ViewLoader } from '#/router/viewRegistry'\nimport { vAuth } from '#/directives/auth'\nimport { setupIcons } from '#/lib/icons'\nimport { reloadOnChunkError, reloadOnce } from '#/lib/chunkReload'\nimport { runtime, type BrandOptions } from '#/lib/runtime'\nimport { useSite } from '#/composables/useSite'\n\n/**\n * 一个插件(或应用自身)能往内核里注册的东西。每一项都是「消费方在自己的代码里写 import.meta.glob,\n * 把结果交进来」—— 内核是预编译的包,自己扫不到消费方的文件。\n */\nexport interface SmartAdminPlugin {\n /** 页面表:`import.meta.glob('./views/**\\/*.vue')`。键取 `/views/` 之后去掉 .vue(如 system/user/index),同名覆盖先注册者。 */\n views?: Record<string, ViewLoader>\n /** 语言包:`import.meta.glob('./locales/ext/*\\/*.ts', { eager: true })`,键尾形如 `<locale>/<命名空间>.ts`,按命名空间深合并。 */\n locales?: Record<string, ExtModule>\n /** 布局壳之外的顶级静态路由(整屏看板、独立打印页等)。 */\n routes?: RouteRecordRaw[]\n /** 菜单 path → i18n key(存量库菜单标题是中文时用)。 */\n menuTitles?: Record<string, string>\n /** 本地 SVG 图标:`import.meta.glob('./assets/svg/*.svg', { query: '?raw', import: 'default', eager: true })`。 */\n icons?: Record<string, string>\n /** 需要 app 实例的注册动作(registerHeaderTool / registerMenuBadge / 自己的 app.use 等),在 mount 前调用。 */\n install?: (app: App) => void\n}\n\nexport interface SmartAdminOptions extends SmartAdminPlugin {\n /** 插件按顺序注册,后者覆盖前者;应用自身(本对象的 views/locales 等)最后注册,优先级最高。 */\n plugins?: SmartAdminPlugin[]\n /** API 根地址;默认空串 = 同源。跨源时后端还要配 SmartAdmin:Api:Cors:AllowedOrigins。 */\n apiBase?: string\n /** 登录页页脚展示的版本号;默认内核版本。 */\n version?: string\n /** 开发态(登录页预填超管账号、设置抽屉「复制配置」);消费方传 import.meta.env.DEV。 */\n dev?: boolean\n /** 品牌默认值:sys.site.* 配置有值时以配置为准,为空时用这里。 */\n brand?: BrandOptions\n /** SmartTable 全局默认。 */\n table?: { density?: 'comfortable' | 'compact'; pageSizes?: number[] }\n}\n\nexport interface SmartAdminApp {\n app: App\n router: typeof router\n pinia: Pinia\n i18n: typeof i18n\n /** 挂载到选择器或元素;返回 Vue app 实例。 */\n mount: (target: string | Element) => App\n}\n\n/**\n * 组装内核应用:写入运行期配置 → 并入插件与应用的页面/文案/路由/图标 → 建 pinia/router/i18n → 返回可挂载的 app。\n * 按 内核内置 < 插件(按数组顺序)< 应用自身 的顺序注册,同 key 后者覆盖前者。结果与后端相同(应用压过内核),\n * 机制相反:后端 TryAdd 是消费方先注册者胜。\n */\nexport function createSmartAdmin(options: SmartAdminOptions = {}): SmartAdminApp {\n // apiBase 写入即生效:各处客户端在下一次调用时按新地址重建(见 api/client.ts 的 followApiBase)\n runtime.apiBase = (options.apiBase ?? '').replace(/\\/+$/, '')\n if (options.version) runtime.version = options.version\n runtime.dev = !!options.dev\n runtime.brand = options.brand ?? {}\n if (options.brand?.title) useSite().site.title = options.brand.title\n\n const layers: SmartAdminPlugin[] = [...(options.plugins ?? []), options]\n const icons: Record<string, string> = {}\n for (const layer of layers) {\n if (layer.views) registerViews(layer.views)\n if (layer.locales) registerLocales(layer.locales)\n if (layer.menuTitles) registerMenuTitles(layer.menuTitles)\n if (layer.icons) Object.assign(icons, layer.icons)\n for (const route of layer.routes ?? []) router.addRoute(route)\n }\n setupIcons(icons) // 注册离线图标集(ph 子集同步入库)+ 本地 SVG,不预热整集\n\n // 全局兜底:没有这两个监听的话,未捕获异常和发版后旧 chunk 404 都是白屏,控制台之外不留痕迹。\n // 渲染期异常由内容区的 ErrorBoundary 收口,这里管它够不着的两类:游离的 Promise 拒绝与预加载失败。\n window.addEventListener('unhandledrejection', e => {\n if (reloadOnChunkError(e.reason)) e.preventDefault()\n })\n // Vite 预加载新 chunk 失败时派发;能走到这个事件就一定是 chunk 问题,不必再判文案。\n window.addEventListener('vite:preloadError', e => {\n if (reloadOnce()) e.preventDefault()\n })\n\n const pinia = createPinia()\n pinia.use(piniaPluginPersistedstate)\n\n const app = createApp(AppRoot)\n app.use(pinia) // 必须在 router 之前:守卫用到 store\n // 深浅色默认 themeScheme:'auto' → 首访自动跟随系统,用户手选 light/dark 后固定(见 stores/app.ts)。\n app.use(router)\n app.use(i18n)\n app.directive('auth', vAuth)\n app.config.errorHandler = (err, _instance, info) => {\n if (reloadOnChunkError(err)) return\n console.error('[SmartAdmin] 未捕获异常', info, err)\n }\n\n // SmartTable 全局默认:labels 一次注入,各页面不用手传 :labels(切语言即时生效)。\n // 只能在这里注入一次:Vue 的 inject 取最近的 provide、不合并 —— 在 App.vue 或任何祖先组件里\n // 再 provide 一次 SMART_TABLE_DEFAULTS,会把这里的 labels/density 整份挡掉。要加默认值就往下面这个对象里加。\n app.provide(\n SMART_TABLE_DEFAULTS,\n createSmartTableDefaults({\n // 表格工具栏密度默认使用紧凑档;已有 storage-key 的用户选择仍按本地缓存优先。\n density: options.table?.density ?? 'compact',\n // 内置兜底是 [10,20,50],后台列表动辄上千行,翻页翻不过来。\n // 上限 200:内核 Api:MaxPageSize 的默认值,选更大的后端会回 48001。\n // 注意 SmartTable 的**初始**每页条数走 default-page-size prop(不在这份可注入的 defaults 里),\n // 没传那个 prop 的页面初始仍是 10,条数选择器会显示一个不在列表里的值。\n pageSizes: options.table?.pageSizes ?? [20, 50, 100, 200],\n labels: computed(() => {\n void i18n.global.locale.value // 触发 locale 依赖收集\n const t = i18n.global.t\n return {\n search: t('common.search'),\n reset: t('common.reset'),\n refresh: t('table.refresh'),\n density: t('app.density'),\n densityComfortable: t('app.comfortable'),\n densityCompact: t('app.compact'),\n columnSettings: t('table.columnSettings'),\n columnSettingsReset: t('table.columnSettingsReset'),\n fixedLeft: t('table.fixedLeft'),\n fixedRight: t('table.fixedRight'),\n fixedNone: t('table.fixedNone'),\n expand: t('table.expand'),\n collapse: t('table.collapse'),\n }\n }),\n }),\n )\n\n for (const layer of layers) layer.install?.(app)\n\n return {\n app,\n router,\n pinia,\n i18n,\n mount: target => {\n app.mount(target)\n return app\n },\n }\n}\n"],"names":["createSmartAdmin","options","runtime","_a","useSite","layers","icons","layer","registerViews","registerLocales","registerMenuTitles","route","router","setupIcons","reloadOnChunkError","reloadOnce","pinia","createPinia","piniaPluginPersistedstate","app","createApp","AppRoot","i18n","vAuth","err","_instance","info","SMART_TABLE_DEFAULTS","createSmartTableDefaults","_b","_c","computed","t","_d","target"],"mappings":";;;;;;;;;;;;;;AAgEO,SAASA,EAAiBC,IAA6B,IAAmB;;AAE/E,EAAAC,EAAQ,WAAWD,EAAQ,WAAW,IAAI,QAAQ,QAAQ,EAAE,GACxDA,EAAQ,YAASC,EAAQ,UAAUD,EAAQ,UAC/CC,EAAQ,MAAM,CAAC,CAACD,EAAQ,KACxBC,EAAQ,QAAQD,EAAQ,SAAS,CAAA,IAC7BE,IAAAF,EAAQ,UAAR,QAAAE,EAAe,UAAOC,EAAA,EAAU,KAAK,QAAQH,EAAQ,MAAM;AAE/D,QAAMI,IAA6B,CAAC,GAAIJ,EAAQ,WAAW,CAAA,GAAKA,CAAO,GACjEK,IAAgC,CAAA;AACtC,aAAWC,KAASF,GAAQ;AAC1B,IAAIE,EAAM,SAAOC,EAAcD,EAAM,KAAK,GACtCA,EAAM,WAASE,EAAgBF,EAAM,OAAO,GAC5CA,EAAM,cAAYG,EAAmBH,EAAM,UAAU,GACrDA,EAAM,SAAO,OAAO,OAAOD,GAAOC,EAAM,KAAK;AACjD,eAAWI,KAASJ,EAAM,UAAU,CAAA,EAAI,CAAAK,EAAO,SAASD,CAAK;AAAA,EAC/D;AACA,EAAAE,EAAWP,CAAK,GAIhB,OAAO,iBAAiB,sBAAsB,CAAA,MAAK;AACjD,IAAIQ,EAAmB,EAAE,MAAM,OAAK,eAAA;AAAA,EACtC,CAAC,GAED,OAAO,iBAAiB,qBAAqB,CAAA,MAAK;AAChD,IAAIC,EAAA,KAAc,EAAE,eAAA;AAAA,EACtB,CAAC;AAED,QAAMC,IAAQC,EAAA;AACd,EAAAD,EAAM,IAAIE,CAAyB;AAEnC,QAAMC,IAAMC,EAAUC,CAAO;AAC7B,EAAAF,EAAI,IAAIH,CAAK,GAEbG,EAAI,IAAIP,CAAM,GACdO,EAAI,IAAIG,CAAI,GACZH,EAAI,UAAU,QAAQI,CAAK,GAC3BJ,EAAI,OAAO,eAAe,CAACK,GAAKC,GAAWC,MAAS;AAClD,IAAIZ,EAAmBU,CAAG,KAC1B,QAAQ,MAAM,sBAAsBE,GAAMF,CAAG;AAAA,EAC/C,GAKAL,EAAI;AAAA,IACFQ;AAAA,IACAC,EAAyB;AAAA;AAAA,MAEvB,WAASC,IAAA5B,EAAQ,UAAR,gBAAA4B,EAAe,YAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKnC,aAAWC,IAAA7B,EAAQ,UAAR,gBAAA6B,EAAe,cAAa,CAAC,IAAI,IAAI,KAAK,GAAG;AAAA,MACxD,QAAQC,EAAS,MAAM;AACrB,QAAKT,EAAK,OAAO,OAAO;AACxB,cAAMU,IAAIV,EAAK,OAAO;AACtB,eAAO;AAAA,UACL,QAAQU,EAAE,eAAe;AAAA,UACzB,OAAOA,EAAE,cAAc;AAAA,UACvB,SAASA,EAAE,eAAe;AAAA,UAC1B,SAASA,EAAE,aAAa;AAAA,UACxB,oBAAoBA,EAAE,iBAAiB;AAAA,UACvC,gBAAgBA,EAAE,aAAa;AAAA,UAC/B,gBAAgBA,EAAE,sBAAsB;AAAA,UACxC,qBAAqBA,EAAE,2BAA2B;AAAA,UAClD,WAAWA,EAAE,iBAAiB;AAAA,UAC9B,YAAYA,EAAE,kBAAkB;AAAA,UAChC,WAAWA,EAAE,iBAAiB;AAAA,UAC9B,QAAQA,EAAE,cAAc;AAAA,UACxB,UAAUA,EAAE,gBAAgB;AAAA,QAAA;AAAA,MAEhC,CAAC;AAAA,IAAA,CACF;AAAA,EAAA;AAGH,aAAWzB,KAASF,EAAQ,EAAA4B,IAAA1B,EAAM,YAAN,QAAA0B,EAAA,KAAA1B,GAAgBY;AAE5C,SAAO;AAAA,IACL,KAAAA;AAAA,IACA,QAAAP;AAAA,IACA,OAAAI;AAAA,IACA,MAAAM;AAAA,IACA,OAAO,CAAAY,OACLf,EAAI,MAAMe,CAAM,GACTf;AAAA,EACT;AAEJ;"}
1
+ {"version":3,"file":"createSmartAdmin.js","sources":["../src/createSmartAdmin.ts"],"sourcesContent":["import { computed, createApp, type App } from 'vue'\nimport { createPinia, type Pinia } from 'pinia'\nimport piniaPluginPersistedstate from 'pinia-plugin-persistedstate'\nimport type { RouteRecordRaw } from 'vue-router'\nimport { SMART_TABLE_DEFAULTS, createSmartTableDefaults } from 'smart-naive-table'\nimport AppRoot from '#/App.vue'\nimport { router } from '#/router'\nimport { i18n, registerLocales, type ExtModule } from '#/locales'\nimport { registerMenuTitles } from '#/locales/menuTitle'\nimport { registerViews, type ViewLoader } from '#/router/viewRegistry'\nimport { vAuth } from '#/directives/auth'\nimport { setupIcons } from '#/lib/icons'\nimport { reloadOnChunkError, reloadOnce } from '#/lib/chunkReload'\nimport { runtime, type BrandOptions } from '#/lib/runtime'\nimport { useSite } from '#/composables/useSite'\nimport { trimTrailingSlashes } from '#/utils/url'\n\n/**\n * 一个插件(或应用自身)能往内核里注册的东西。页面、语言包、图标这类文件集合由消费方在自己的代码里\n * 写 import.meta.glob、把结果交进来 —— 内核是预编译的包,自己扫不到消费方的文件。\n */\nexport interface SmartAdminPlugin {\n /** 页面表:`import.meta.glob('./views/**\\/*.vue')`。键取 `/views/` 之后去掉 .vue(如 system/user/index),同名覆盖先注册者。 */\n views?: Record<string, ViewLoader>\n /** 语言包:`import.meta.glob('./locales/ext/*\\/*.ts', { eager: true })`,键尾形如 `<locale>/<命名空间>.ts`,按命名空间深合并。 */\n locales?: Record<string, ExtModule>\n /** 布局壳之外的顶级静态路由(整屏看板、独立打印页等)。 */\n routes?: RouteRecordRaw[]\n /** 菜单 path → i18n key(存量库菜单标题是中文时用)。 */\n menuTitles?: Record<string, string>\n /** 本地 SVG 图标:`import.meta.glob('./assets/svg/*.svg', { query: '?raw', import: 'default', eager: true })`。 */\n icons?: Record<string, string>\n /** 需要 app 实例的注册动作(registerHeaderTool / registerMenuBadge / 自己的 app.use 等),在 mount 前调用。 */\n install?: (app: App) => void\n}\n\nexport interface SmartAdminOptions extends SmartAdminPlugin {\n /** 插件按顺序注册,后者覆盖前者;应用自身(本对象的 views/locales 等)最后注册,优先级最高。 */\n plugins?: SmartAdminPlugin[]\n /** API 根地址;默认空串 = 同源。跨源时后端还要配 SmartAdmin:Api:Cors:AllowedOrigins。 */\n apiBase?: string\n /** 登录页页脚展示的版本号;默认内核版本。 */\n version?: string\n /** 开发态(登录页预填超管账号、设置抽屉「复制配置」);消费方传 import.meta.env.DEV。 */\n dev?: boolean\n /** 品牌默认值:sys.site.* 配置有值时以配置为准,为空时用这里。 */\n brand?: BrandOptions\n /** SmartTable 全局默认。 */\n table?: { density?: 'comfortable' | 'compact'; pageSizes?: number[] }\n}\n\nexport interface SmartAdminApp {\n app: App\n router: typeof router\n pinia: Pinia\n i18n: typeof i18n\n /** 挂载到选择器或元素;返回 Vue app 实例。 */\n mount: (target: string | Element) => App\n}\n\n/**\n * 组装内核应用:写入运行期配置 → 并入插件与应用的页面/文案/路由/图标 → 建 pinia/router/i18n → 返回可挂载的 app。\n * 按 内核内置 < 插件(按数组顺序)< 应用自身 的顺序注册,同 key 后者覆盖前者。结果与后端相同(应用压过内核),\n * 机制相反:后端 TryAdd 是消费方先注册者胜。\n */\nexport function createSmartAdmin(options: SmartAdminOptions = {}): SmartAdminApp {\n // apiBase 写入即生效:各处客户端在下一次调用时按新地址重建(见 api/client.ts 的 followApiBase)\n runtime.apiBase = trimTrailingSlashes(options.apiBase ?? '')\n if (options.version) runtime.version = options.version\n runtime.dev = !!options.dev\n runtime.brand = options.brand ?? {}\n if (options.brand?.title) useSite().site.title = options.brand.title\n\n const layers: SmartAdminPlugin[] = [...(options.plugins ?? []), options]\n const icons: Record<string, string> = {}\n for (const layer of layers) {\n if (layer.views) registerViews(layer.views)\n if (layer.locales) registerLocales(layer.locales)\n if (layer.menuTitles) registerMenuTitles(layer.menuTitles)\n if (layer.icons) Object.assign(icons, layer.icons)\n for (const route of layer.routes ?? []) router.addRoute(route)\n }\n setupIcons(icons) // 注册离线图标集(ph 子集同步入库)+ 本地 SVG,不预热整集\n\n // 全局兜底:没有这两个监听的话,未捕获异常和发版后旧 chunk 404 都是白屏,控制台之外不留痕迹。\n // 渲染期异常由内容区的 ErrorBoundary 收口,这里管它够不着的两类:游离的 Promise 拒绝与预加载失败。\n window.addEventListener('unhandledrejection', e => {\n if (reloadOnChunkError(e.reason)) e.preventDefault()\n })\n // Vite 预加载新 chunk 失败时派发;能走到这个事件就一定是 chunk 问题,不必再判文案。\n window.addEventListener('vite:preloadError', e => {\n if (reloadOnce()) e.preventDefault()\n })\n\n const pinia = createPinia()\n pinia.use(piniaPluginPersistedstate)\n\n const app = createApp(AppRoot)\n app.use(pinia) // 必须在 router 之前:守卫用到 store\n // 深浅色默认 themeScheme:'auto' → 首访自动跟随系统,用户手选 light/dark 后固定(见 stores/app.ts)。\n app.use(router)\n app.use(i18n)\n app.directive('auth', vAuth)\n app.config.errorHandler = (err, _instance, info) => {\n if (reloadOnChunkError(err)) return\n console.error('[SmartAdmin] 未捕获异常', info, err)\n }\n\n // SmartTable 全局默认:labels 一次注入,各页面不用手传 :labels(切语言即时生效)。\n // 只能在这里注入一次:Vue 的 inject 取最近的 provide、不合并 —— 在 App.vue 或任何祖先组件里\n // 再 provide 一次 SMART_TABLE_DEFAULTS,会把这里的 labels/density 整份挡掉。要加默认值就往下面这个对象里加。\n app.provide(\n SMART_TABLE_DEFAULTS,\n createSmartTableDefaults({\n // 表格工具栏密度默认使用紧凑档;已有 storage-key 的用户选择仍按本地缓存优先。\n density: options.table?.density ?? 'compact',\n // 内置兜底是 [10,20,50],后台列表动辄上千行,翻页翻不过来。\n // 上限 200:内核 Api:MaxPageSize 的默认值,选更大的后端会回 48001。\n // 注意 SmartTable 的**初始**每页条数走 default-page-size prop(不在这份可注入的 defaults 里),\n // 没传那个 prop 的页面初始仍是 10,条数选择器会显示一个不在列表里的值。\n pageSizes: options.table?.pageSizes ?? [20, 50, 100, 200],\n labels: computed(() => {\n void i18n.global.locale.value // 触发 locale 依赖收集\n const t = i18n.global.t\n return {\n search: t('common.search'),\n reset: t('common.reset'),\n refresh: t('table.refresh'),\n density: t('app.density'),\n densityComfortable: t('app.comfortable'),\n densityCompact: t('app.compact'),\n columnSettings: t('table.columnSettings'),\n columnSettingsReset: t('table.columnSettingsReset'),\n fixedLeft: t('table.fixedLeft'),\n fixedRight: t('table.fixedRight'),\n fixedNone: t('table.fixedNone'),\n expand: t('table.expand'),\n collapse: t('table.collapse'),\n }\n }),\n }),\n )\n\n for (const layer of layers) layer.install?.(app)\n\n return {\n app,\n router,\n pinia,\n i18n,\n mount: target => {\n app.mount(target)\n return app\n },\n }\n}\n"],"names":["createSmartAdmin","options","runtime","trimTrailingSlashes","_a","useSite","layers","icons","layer","registerViews","registerLocales","registerMenuTitles","route","router","setupIcons","reloadOnChunkError","reloadOnce","pinia","createPinia","piniaPluginPersistedstate","app","createApp","AppRoot","i18n","vAuth","err","_instance","info","SMART_TABLE_DEFAULTS","createSmartTableDefaults","_b","_c","computed","t","_d","target"],"mappings":";;;;;;;;;;;;;;;AAiEO,SAASA,EAAiBC,IAA6B,IAAmB;;AAE/E,EAAAC,EAAQ,UAAUC,EAAoBF,EAAQ,WAAW,EAAE,GACvDA,EAAQ,YAASC,EAAQ,UAAUD,EAAQ,UAC/CC,EAAQ,MAAM,CAAC,CAACD,EAAQ,KACxBC,EAAQ,QAAQD,EAAQ,SAAS,CAAA,IAC7BG,IAAAH,EAAQ,UAAR,QAAAG,EAAe,UAAOC,EAAA,EAAU,KAAK,QAAQJ,EAAQ,MAAM;AAE/D,QAAMK,IAA6B,CAAC,GAAIL,EAAQ,WAAW,CAAA,GAAKA,CAAO,GACjEM,IAAgC,CAAA;AACtC,aAAWC,KAASF,GAAQ;AAC1B,IAAIE,EAAM,SAAOC,EAAcD,EAAM,KAAK,GACtCA,EAAM,WAASE,EAAgBF,EAAM,OAAO,GAC5CA,EAAM,cAAYG,EAAmBH,EAAM,UAAU,GACrDA,EAAM,SAAO,OAAO,OAAOD,GAAOC,EAAM,KAAK;AACjD,eAAWI,KAASJ,EAAM,UAAU,CAAA,EAAI,CAAAK,EAAO,SAASD,CAAK;AAAA,EAC/D;AACA,EAAAE,EAAWP,CAAK,GAIhB,OAAO,iBAAiB,sBAAsB,CAAA,MAAK;AACjD,IAAIQ,EAAmB,EAAE,MAAM,OAAK,eAAA;AAAA,EACtC,CAAC,GAED,OAAO,iBAAiB,qBAAqB,CAAA,MAAK;AAChD,IAAIC,EAAA,KAAc,EAAE,eAAA;AAAA,EACtB,CAAC;AAED,QAAMC,IAAQC,EAAA;AACd,EAAAD,EAAM,IAAIE,CAAyB;AAEnC,QAAMC,IAAMC,EAAUC,CAAO;AAC7B,EAAAF,EAAI,IAAIH,CAAK,GAEbG,EAAI,IAAIP,CAAM,GACdO,EAAI,IAAIG,CAAI,GACZH,EAAI,UAAU,QAAQI,CAAK,GAC3BJ,EAAI,OAAO,eAAe,CAACK,GAAKC,GAAWC,MAAS;AAClD,IAAIZ,EAAmBU,CAAG,KAC1B,QAAQ,MAAM,sBAAsBE,GAAMF,CAAG;AAAA,EAC/C,GAKAL,EAAI;AAAA,IACFQ;AAAA,IACAC,EAAyB;AAAA;AAAA,MAEvB,WAASC,IAAA7B,EAAQ,UAAR,gBAAA6B,EAAe,YAAW;AAAA;AAAA;AAAA;AAAA;AAAA,MAKnC,aAAWC,IAAA9B,EAAQ,UAAR,gBAAA8B,EAAe,cAAa,CAAC,IAAI,IAAI,KAAK,GAAG;AAAA,MACxD,QAAQC,EAAS,MAAM;AACrB,QAAKT,EAAK,OAAO,OAAO;AACxB,cAAMU,IAAIV,EAAK,OAAO;AACtB,eAAO;AAAA,UACL,QAAQU,EAAE,eAAe;AAAA,UACzB,OAAOA,EAAE,cAAc;AAAA,UACvB,SAASA,EAAE,eAAe;AAAA,UAC1B,SAASA,EAAE,aAAa;AAAA,UACxB,oBAAoBA,EAAE,iBAAiB;AAAA,UACvC,gBAAgBA,EAAE,aAAa;AAAA,UAC/B,gBAAgBA,EAAE,sBAAsB;AAAA,UACxC,qBAAqBA,EAAE,2BAA2B;AAAA,UAClD,WAAWA,EAAE,iBAAiB;AAAA,UAC9B,YAAYA,EAAE,kBAAkB;AAAA,UAChC,WAAWA,EAAE,iBAAiB;AAAA,UAC9B,QAAQA,EAAE,cAAc;AAAA,UACxB,UAAUA,EAAE,gBAAgB;AAAA,QAAA;AAAA,MAEhC,CAAC;AAAA,IAAA,CACF;AAAA,EAAA;AAGH,aAAWzB,KAASF,EAAQ,EAAA4B,IAAA1B,EAAM,YAAN,QAAA0B,EAAA,KAAA1B,GAAgBY;AAE5C,SAAO;AAAA,IACL,KAAAA;AAAA,IACA,QAAAP;AAAA,IACA,OAAAI;AAAA,IACA,MAAAM;AAAA,IACA,OAAO,CAAAY,OACLf,EAAI,MAAMe,CAAM,GACTf;AAAA,EACT;AAEJ;"}