@epxing/core 5.3.0-beta.2 → 5.3.0-beta.4

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 (100) hide show
  1. package/API.md +715 -714
  2. package/README.md +3 -1
  3. package/dist/.cache/route-chart-release/frontend/frame/public/js/ecStat.min.js +4 -0
  4. package/dist/.cache/route-chart-release/frontend/frame/public/js/echarts-wordcloud.min.js +6 -0
  5. package/dist/.cache/route-chart-release/frontend/frame/public/js/echarts.min.js +49 -0
  6. package/dist/_virtual/_plugin-vue_export-helper.js +10 -0
  7. package/dist/_virtual/jsencrypt.min.js +1 -1
  8. package/dist/assets/role_select-DTqRlFNz.webp +0 -0
  9. package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/jsencrypt@3.5.4/_vendor/jsencrypt/bin/jsencrypt.min.js +1 -1
  10. package/dist/src/RootApp.js +54 -0
  11. package/dist/src/api/createHttp.js +6 -0
  12. package/dist/src/app-ui-settings.js +49 -0
  13. package/dist/src/axios.js +10 -15
  14. package/dist/src/chart-runtime-assets.js +19 -0
  15. package/dist/src/chart-runtime-sources.js +8 -0
  16. package/dist/src/create-app.js +3 -6
  17. package/dist/src/index.js +2 -0
  18. package/dist/src/loadComponent.js +2 -2
  19. package/dist/src/menu-route.js +11 -0
  20. package/dist/src/mixins/component.js +3 -0
  21. package/dist/src/navigation-context.js +47 -0
  22. package/dist/src/navigation.js +43 -12
  23. package/dist/src/package-install.js +3 -2
  24. package/dist/src/plugins/element-plus.js +8 -8
  25. package/dist/src/public-api.js +4 -2
  26. package/dist/src/route-components.js +9 -0
  27. package/dist/src/router/config.js +7 -8
  28. package/dist/src/runtime-services.js +1 -1
  29. package/dist/src/runtime-utils/meta.js +3 -0
  30. package/dist/src/runtime-utils/pdf.viewer.js +31 -15
  31. package/dist/src/runtime-utils/util.js +28 -5
  32. package/dist/src/shared-packages.js +1 -3
  33. package/dist/src/store/modules/list.js +25 -3
  34. package/dist/src/store/modules/meta.js +49 -7
  35. package/dist/src/style-presets.js +4 -4
  36. package/dist/src/ui-settings.js +90 -18
  37. package/dist/src/views/App.js +48 -0
  38. package/dist/src/views/AppRoleSelect.js +233 -0
  39. package/dist/src/views/Error.js +210 -0
  40. package/dist/src/views/Module.js +173 -0
  41. package/dist/style.css +251 -0
  42. package/package.json +4 -4
  43. package/src/RootApp.vue +75 -0
  44. package/src/api/createHttp.js +7 -0
  45. package/src/api/index.js +47 -47
  46. package/src/app-ui-settings.js +31 -0
  47. package/src/axios.js +311 -318
  48. package/src/chart-runtime-assets.js +13 -0
  49. package/src/chart-runtime-sources.js +5 -0
  50. package/src/create-app.js +3 -7
  51. package/src/index.js +28 -28
  52. package/src/install.js +157 -157
  53. package/src/loadComponent.js +187 -187
  54. package/src/loadLibrary.js +99 -99
  55. package/src/menu-route.js +12 -1
  56. package/src/mixins/component.js +1464 -1461
  57. package/src/navigation-context.js +49 -0
  58. package/src/navigation.js +208 -176
  59. package/src/package-install.js +2 -1
  60. package/src/plugins/element-plus.js +302 -302
  61. package/src/plugins/frame.js +178 -178
  62. package/src/public-api.js +1 -0
  63. package/src/route-components.js +7 -0
  64. package/src/router/config.js +861 -864
  65. package/src/router/public.js +1 -1
  66. package/src/runtime-services.js +1 -1
  67. package/src/runtime-utils/fieldFactory.js +179 -179
  68. package/src/runtime-utils/meta.js +399 -378
  69. package/src/runtime-utils/pdf.viewer.js +672 -644
  70. package/src/runtime-utils/util.js +1359 -1330
  71. package/src/shared-packages.js +1 -3
  72. package/src/store/modules/config.js +351 -351
  73. package/src/store/modules/list.js +30 -3
  74. package/src/store/modules/locale.js +236 -236
  75. package/src/store/modules/meta.js +314 -265
  76. package/src/style-presets.js +4 -4
  77. package/src/ui-settings.js +84 -23
  78. package/src/views/App.vue +53 -0
  79. package/src/views/AppRoleSelect.vue +334 -0
  80. package/src/views/Error.vue +63 -0
  81. package/src/views/Module.vue +210 -0
  82. package/src/xss.js +26 -26
  83. package/types/app.d.ts +10 -10
  84. package/types/axios.d.ts +1 -1
  85. package/types/component-mixin.d.ts +1 -1
  86. package/types/field-factory.d.ts +1 -1
  87. package/types/global-mixin.d.ts +1 -1
  88. package/types/http.d.ts +1 -1
  89. package/types/index.d.ts +3 -0
  90. package/types/meta.d.ts +1 -1
  91. package/types/navigation.d.ts +21 -21
  92. package/types/pdf-viewer.d.ts +1 -1
  93. package/types/runtime.d.ts +13 -13
  94. package/types/store.d.ts +1 -1
  95. package/types/util.d.ts +2 -2
  96. /package/dist/{build → .cache/route-chart-release/frontend/frame/build}/stubs/empty-components.js +0 -0
  97. /package/dist/{src → .cache/route-chart-release/frontend/frame/src}/element-icons.names.js +0 -0
  98. /package/dist/{src → .cache/route-chart-release/frontend/frame/src}/element-plus.lazy.js +0 -0
  99. /package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/element-plus@2.14.3_vue@3.5.39_typescript@5.9.3_/_vendor/element-plus/dist/locale/en.js +0 -0
  100. /package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/element-plus@2.14.3_vue@3.5.39_typescript@5.9.3_/_vendor/element-plus/dist/locale/zh-cn.js +0 -0
@@ -1,4 +1,5 @@
1
1
  import '../../style.css';
2
+ import { restoreChatWikiRoute } from "@epxing/utils/src/wiki-path";
2
3
  import { runtimeRouteComponent, getRuntimeService, runtimeStore } from "../runtime-services.js";
3
4
  import { isEmpty } from "lodash-es";
4
5
  import util from "../runtime-utils/util.js";
@@ -10,7 +11,6 @@ import { setElementPlusLocale } from "../plugins/element-plus.js";
10
11
  import { MENU_ROUTE_ALIAS, roleStorage, effectiveRouteName } from "../menu-route.js";
11
12
  const Page = runtimeRouteComponent("Page");
12
13
  const Login = runtimeRouteComponent("Login");
13
- const QyWechat = runtimeRouteComponent("QyWechat");
14
14
  const App = runtimeRouteComponent("App");
15
15
  const WikiSitePage = runtimeRouteComponent("WikiSitePage");
16
16
  const Module = runtimeRouteComponent("Module");
@@ -239,9 +239,11 @@ const routes = [
239
239
  props: (route) => ({ query: route.query })
240
240
  },
241
241
  {
242
- path: "/system/:client/:system/:role?",
242
+ // 32 位菜单 ID 与旧角色编号消歧;旧的 /system/.../SYSTEM 地址仍可使用。
243
+ path: "/system/:client/:system/:menuKey([0-9A-Fa-f]{32})?/:role?",
243
244
  name: "system",
244
245
  component: App,
246
+ meta: { refresh: false },
245
247
  props: (route) => ({ query: route.query })
246
248
  },
247
249
  {
@@ -317,11 +319,6 @@ const routes = [
317
319
  component: App,
318
320
  props: (route) => ({ query: route.query })
319
321
  },
320
- {
321
- path: "/qywechat",
322
- name: "qywechat",
323
- component: QyWechat
324
- },
325
322
  {
326
323
  // 旧形态 /m/:menuKey 链接兼容:重定向到无 /m/ 的新形态
327
324
  path: "/app/:client/:system/:app/m/:menuKey",
@@ -344,6 +341,8 @@ const routes = [
344
341
  }
345
342
  ];
346
343
  const beforeEach = async (to, from, next) => {
344
+ const restoredWikiRoute = restoreChatWikiRoute(to);
345
+ if (restoredWikiRoute) return next(restoredWikiRoute);
347
346
  try {
348
347
  subscribeToLogout();
349
348
  return await runBeforeEach(to, from, next);
@@ -366,7 +365,7 @@ const runBeforeEach = async (to, from, rawNext) => {
366
365
  }
367
366
  return rawNext(location);
368
367
  };
369
- const isMenuRoute = !!MENU_ROUTE_ALIAS[to.name];
368
+ const isMenuRoute = !!MENU_ROUTE_ALIAS[to.name] || to.name === "system" && !!to.params.menuKey;
370
369
  if (isMenuRoute) {
371
370
  const storedRole = roleStorage.get(to.params.client, to.params.system);
372
371
  to = { ...to, name: effectiveRouteName(to.name), params: { ...to.params, role: to.params.role || storedRole || void 0 } };
@@ -34,7 +34,7 @@ function registerRouteComponents(components) {
34
34
  function runtimeRouteComponent(name) {
35
35
  return async () => {
36
36
  const component = routeComponents.get(name);
37
- if (!component) throw new Error(`[epx/core] 路由组件 ${name} 尚未注册,请安装 component/business 或传入 routeComponents`);
37
+ if (!component) throw new Error(`[epx/core] 路由组件 ${name} 尚未注册,请安装提供该页面的 components/business 包或传入 routeComponents`);
38
38
  return component;
39
39
  };
40
40
  }
@@ -1,5 +1,6 @@
1
1
  import '../../style.css';
2
2
  import util from "./util.js";
3
+ const appliedLanguage = /* @__PURE__ */ new WeakMap();
3
4
  class Meta {
4
5
  constructor(props = {}) {
5
6
  var me = this;
@@ -56,6 +57,8 @@ class Meta {
56
57
  }
57
58
  getAttributes() {
58
59
  const currentLanguage = util.getCurrentLanguage();
60
+ if (appliedLanguage.get(this) === currentLanguage) return this.attributes;
61
+ appliedLanguage.set(this, currentLanguage);
59
62
  for (const key in this.attributes) {
60
63
  const item = this.attributes[key];
61
64
  for (const key2 in item.languageMap) {
@@ -1,6 +1,11 @@
1
1
  import '../../style.css';
2
2
  import util from "./util.js";
3
3
  const DEFAULT_CACHE_SIZE = 10;
4
+ const MAX_CANVAS_PIXELS = 16777216;
5
+ function releaseCanvas(canvas) {
6
+ canvas.width = 0;
7
+ canvas.height = 0;
8
+ }
4
9
  function binarySearchFirstItem(items, condition) {
5
10
  var minIndex = 0;
6
11
  var maxIndex = items.length - 1;
@@ -167,18 +172,18 @@ class PdfViewer {
167
172
  }
168
173
  this.scrollState = null;
169
174
  for (const item of this.pages) {
170
- if (item.div && item.div.parentNode) item.div.parentNode.removeChild(item.div);
175
+ if (!item.div) continue;
176
+ for (const canvas of item.div.querySelectorAll("canvas")) releaseCanvas(canvas);
177
+ if (item.div.parentNode) item.div.parentNode.removeChild(item.div);
171
178
  }
172
179
  this.pages = [];
173
180
  this.loadViews = [];
174
181
  this.searchTextArr = [];
175
- if (this.pdfDocument) {
176
- try {
177
- this.pdfDocument.destroy();
178
- } catch (e) {
179
- }
180
- this.pdfDocument = null;
182
+ if (this.loadingTask) {
183
+ this.loadingTask.destroy();
184
+ this.loadingTask = null;
181
185
  }
186
+ this.pdfDocument = null;
182
187
  }
183
188
  async searchTextInPDF(searchText) {
184
189
  const numPages = this.pdfDocument.numPages;
@@ -296,12 +301,16 @@ class PdfViewer {
296
301
  pageRotate: 0
297
302
  };
298
303
  await this.loadPdf();
299
- return this.pdfJs.getDocument({
304
+ if (this.destroyed) return;
305
+ this.loadingTask = this.pdfJs.getDocument({
300
306
  url: this.pdfConfig.url,
301
307
  cMapUrl: this.pdfConfig.cMapUrl,
302
308
  cMapPacked: this.pdfConfig.cMapPacked
303
- }).promise.then((pdfDocument) => {
309
+ });
310
+ return this.loadingTask.promise.then((pdfDocument) => {
304
311
  this.load(pdfDocument);
312
+ }).catch((err) => {
313
+ if (!this.destroyed) throw err;
305
314
  });
306
315
  }
307
316
  load(pdfDocument) {
@@ -424,6 +433,7 @@ class PdfViewer {
424
433
  if (node.className.includes("pdf-viewer-loading")) {
425
434
  node.style.display = "block";
426
435
  } else {
436
+ if (node.tagName === "CANVAS") releaseCanvas(node);
427
437
  div.removeChild(node);
428
438
  }
429
439
  }
@@ -435,18 +445,18 @@ class PdfViewer {
435
445
  if (div.getAttribute("data-loaded")) continue;
436
446
  div.setAttribute("data-loaded", true);
437
447
  this.pdfDocument.getPage(item.view.id).then((page) => {
448
+ if (this.destroyed) return;
438
449
  const canvas = document.createElement("canvas");
439
450
  const ctx = canvas.getContext("2d", {
440
451
  alpha: false
441
452
  });
442
453
  const initViewport = page.getViewport({ scale: 1 });
443
- let scale = 1;
444
- if (this.pdfConfig.zoom === "page-width") {
445
- scale = Math.ceil(document.body.offsetWidth / initViewport.width);
446
- } else {
447
- scale = this.pdfConfig.zoom;
448
- }
449
454
  const rotation = (this.pdfConfig.pageRotate + initViewport.rotation) % 360;
455
+ const baseViewport = page.getViewport({ scale: 1, rotation });
456
+ const cssWidth = div.clientWidth || this.pdfConfig.viewerContainer.offsetWidth || baseViewport.width;
457
+ let scale = cssWidth / baseViewport.width * (window.devicePixelRatio || 1);
458
+ const pixels = baseViewport.width * baseViewport.height * scale * scale;
459
+ if (pixels > MAX_CANVAS_PIXELS) scale *= Math.sqrt(MAX_CANVAS_PIXELS / pixels);
450
460
  const viewport = page.getViewport({ scale, rotation });
451
461
  canvas.width = viewport.width;
452
462
  canvas.height = viewport.height;
@@ -459,11 +469,17 @@ class PdfViewer {
459
469
  };
460
470
  var renderTask = page.render(renderContext);
461
471
  renderTask.promise.then(() => {
472
+ if (this.destroyed) {
473
+ releaseCanvas(canvas);
474
+ return;
475
+ }
462
476
  const loading = item.view.div.querySelector(".pdf-viewer-loading");
463
477
  loading.style.display = "none";
464
478
  item.view.div.appendChild(canvas);
465
479
  this.loadViews.push(item.view);
466
480
  cb && cb();
481
+ }, () => {
482
+ releaseCanvas(canvas);
467
483
  });
468
484
  });
469
485
  }
@@ -7,6 +7,7 @@ import * as formula from "@epxing/utils/src/formula";
7
7
  import { evalWith } from "@epxing/utils/src/eval-with";
8
8
  import { useVuePlugin, getApp } from "../app.js";
9
9
  import { resolveRouteRole } from "../menu-route.js";
10
+ import { loadChartRuntimeAssets } from "../chart-runtime-assets.js";
10
11
  const installingLibraries = /* @__PURE__ */ new Map();
11
12
  class Util {
12
13
  constructor() {
@@ -585,7 +586,8 @@ class Util {
585
586
  }
586
587
  script.onerror = function() {
587
588
  console.error("[util.loadJS] 加载失败:" + url);
588
- callback();
589
+ if (options.onerror) options.onerror(new Error("脚本加载失败:" + url));
590
+ else callback();
589
591
  };
590
592
  script.src = url;
591
593
  document.getElementsByTagName("head")[0].appendChild(script);
@@ -788,7 +790,7 @@ class Util {
788
790
  const publicPath = this.getPublicPath();
789
791
  const jsDir = this.getStaticJsDir();
790
792
  if (plugin === "luckysheet") {
791
- const loadPromise = loadFile([publicPath + "luckysheet/pluginsCss.css", publicPath + "luckysheet/plugins.css", publicPath + "luckysheet/luckysheet.css", publicPath + "/luckysheet/iconfont.css", publicPath + "luckysheet/plugin.js", publicPath + "luckysheet/luckysheet.umd.js"]).then(() => globalThis.luckysheet);
793
+ const loadPromise = loadFile([publicPath + "luckysheet/pluginsCss.css", publicPath + "luckysheet/plugins.css", publicPath + "luckysheet/luckysheet.css", publicPath + "luckysheet/iconfont.css", publicPath + "luckysheet/plugin.js", publicPath + "luckysheet/luckysheet.umd.js"]).then(() => globalThis.luckysheet);
792
794
  this.loadPlugins[plugin] = loadPromise;
793
795
  return loadPromise;
794
796
  } else if (plugin === "BMap") {
@@ -828,7 +830,26 @@ class Util {
828
830
  this.loadPlugins[plugin] = loadPromise;
829
831
  return loadPromise;
830
832
  } else if (plugin === "echarts") {
831
- const loadPromise = loadFile([publicPath + jsDir + "echarts.min.js?v=6.0.0-full", publicPath + jsDir + "ecStat.min.js"]).then(() => globalThis.echarts);
833
+ const loading = window.STATIC_JS_DIR ? Promise.all([publicPath + jsDir + "echarts.min.js?v=6.0.0-full", publicPath + jsDir + "ecStat.min.js"].map((url) => new Promise((resolve, reject) => {
834
+ this.loadJS({ url, callback: resolve, onerror: reject });
835
+ }))) : loadChartRuntimeAssets(this.loadJS.bind(this));
836
+ const loadPromise = loading.then(() => {
837
+ if (!globalThis.echarts || !globalThis.ecStat) throw new Error("图表资源加载失败,请检查 ECharts 和 ecStat 脚本");
838
+ return globalThis.echarts;
839
+ }).catch((error) => {
840
+ delete this.loadPlugins[plugin];
841
+ throw error;
842
+ });
843
+ this.loadPlugins[plugin] = loadPromise;
844
+ return loadPromise;
845
+ } else if (plugin === "echarts-wordcloud") {
846
+ const loading = window.STATIC_JS_DIR ? new Promise((resolve, reject) => {
847
+ this.loadJS({ url: publicPath + jsDir + "echarts-wordcloud.min.js", callback: resolve, onerror: reject });
848
+ }) : loadChartRuntimeAssets(this.loadJS.bind(this), ["wordcloud"]);
849
+ const loadPromise = loading.catch((error) => {
850
+ delete this.loadPlugins[plugin];
851
+ throw error;
852
+ });
832
853
  this.loadPlugins[plugin] = loadPromise;
833
854
  return loadPromise;
834
855
  } else if (plugin === "pdf") {
@@ -1054,7 +1075,8 @@ class Util {
1054
1075
  }
1055
1076
  encrypt(content, key, { type } = {}) {
1056
1077
  if (type === "aes") {
1057
- return __epxPackagePreload(() => import("crypto-js"), true ? [] : void 0, import.meta.url).then((CryptoJS) => {
1078
+ return __epxPackagePreload(() => import("crypto-js"), true ? [] : void 0, import.meta.url).then((cryptoModule) => {
1079
+ const CryptoJS = "default" in cryptoModule ? cryptoModule.default : cryptoModule;
1058
1080
  const base64Key = atob(key);
1059
1081
  const aesKey = CryptoJS.enc.Utf8.parse(base64Key.toString(CryptoJS.enc.Utf8));
1060
1082
  const encrypted = CryptoJS.AES.encrypt(content, aesKey, {
@@ -1074,7 +1096,8 @@ class Util {
1074
1096
  }
1075
1097
  decrypt(content, { type, key }) {
1076
1098
  if (type === "aes") {
1077
- return __epxPackagePreload(() => import("crypto-js"), true ? [] : void 0, import.meta.url).then((CryptoJS) => {
1099
+ return __epxPackagePreload(() => import("crypto-js"), true ? [] : void 0, import.meta.url).then((cryptoModule) => {
1100
+ const CryptoJS = "default" in cryptoModule ? cryptoModule.default : cryptoModule;
1078
1101
  const base64Key = atob(key);
1079
1102
  const aesKey = CryptoJS.enc.Utf8.parse(base64Key.toString(CryptoJS.enc.Utf8));
1080
1103
  const decrypted = CryptoJS.AES.decrypt(content, aesKey, {
@@ -4,9 +4,7 @@ function publishSharedPackage(name, api) {
4
4
  const registry = globalThis.__EPX_PACKAGES__ || (globalThis.__EPX_PACKAGES__ = /* @__PURE__ */ Object.create(null));
5
5
  const namespace = registry[name] || (registry[name] = {});
6
6
  Object.assign(namespace, api);
7
- if (name === "@epxing/component" || name === "@epxing/components") {
8
- registry["@epxing/component"] = namespace;
9
- registry["@epxing/components"] = namespace;
7
+ if (name === "@epxing/components") {
10
8
  if (!namespace.install) namespace.install = (app) => installComponents(app, Object.fromEntries(
11
9
  Object.entries(namespace).filter(([key]) => key !== "install" && key !== "default")
12
10
  ));
@@ -1,8 +1,19 @@
1
1
  import '../../../style.css';
2
- import { markRaw } from "vue";
2
+ import { reactive, markRaw } from "vue";
3
3
  import client from "../../api/index.js";
4
4
  import { ADD_TAG, ADD_LIST, ADD_OBJECT_COUNT, ADD_OBJECT } from "../actions.js";
5
5
  import { touchKeys, prune, touch } from "../lru.js";
6
+ const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
7
+ const lists = reactive({});
8
+ const listSignals = /* @__PURE__ */ new Map();
9
+ const signalOf = (key) => {
10
+ let signal = listSignals.get(key);
11
+ if (!signal) {
12
+ signal = reactive({ version: 0 });
13
+ listSignals.set(key, signal);
14
+ }
15
+ return signal;
16
+ };
6
17
  const formatData = (array, values = [], list2) => {
7
18
  return array.map((item) => {
8
19
  item.defaultIndex = 0;
@@ -35,7 +46,7 @@ const markPayloadRaw = (data) => {
35
46
  };
36
47
  const list = {
37
48
  state: {
38
- lists: {},
49
+ lists,
39
50
  treeList: {},
40
51
  objects: {},
41
52
  object_counts: {},
@@ -44,6 +55,7 @@ const list = {
44
55
  },
45
56
  getters: {
46
57
  getCodeById: (state) => (id) => {
58
+ signalOf(id).version;
47
59
  const list2 = state.lists[id];
48
60
  if (list2) {
49
61
  touch("lists", id);
@@ -69,12 +81,22 @@ const list = {
69
81
  getTagListByMetaId: (state) => (id) => state.tags[id]
70
82
  },
71
83
  mutations: {
84
+ // 逐 key 写入并只通知该 key 的信号,不整体替换 lists
72
85
  [ADD_LIST](state, data) {
73
86
  touchKeys("lists", Object.keys(data));
74
- state.lists = prune("lists", { ...state.lists, ...markPayloadRaw(data) }, LIMITS.lists);
87
+ const rawData = markPayloadRaw(data);
88
+ for (const key in rawData) {
89
+ state.lists[key] = rawData[key];
90
+ signalOf(key).version++;
91
+ }
92
+ const keep = prune("lists", { ...state.lists }, LIMITS.lists);
93
+ for (const key in state.lists) {
94
+ if (!hasOwn(keep, key)) delete state.lists[key];
95
+ }
75
96
  },
76
97
  "DELETE_LIST"(state, payload) {
77
98
  delete state.lists[payload.key];
99
+ signalOf(payload.key).version++;
78
100
  },
79
101
  "ADD_TREE_LIST"(state, data) {
80
102
  touchKeys("treeList", Object.keys(data));
@@ -1,10 +1,31 @@
1
1
  import '../../../style.css';
2
- import { markRaw } from "vue";
2
+ import { reactive, markRaw, toRaw } from "vue";
3
3
  import client from "../../api/index.js";
4
4
  import Meta from "../../runtime-utils/meta.js";
5
5
  import { ADD_META } from "../actions.js";
6
6
  import { touch, touchKeys, prune } from "../lru.js";
7
7
  const ADD_SIMPLE_META = "ADD_SIMPLE_META";
8
+ const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
9
+ const metas = reactive({});
10
+ const metaAliases = /* @__PURE__ */ new WeakMap();
11
+ const aliasesOf = (state) => {
12
+ const cache = toRaw(state.metas);
13
+ if (!metaAliases.has(cache)) metaAliases.set(cache, {});
14
+ return metaAliases.get(cache);
15
+ };
16
+ const metaSignals = /* @__PURE__ */ new Map();
17
+ const signalOf = (key) => {
18
+ let signal = metaSignals.get(key);
19
+ if (!signal) {
20
+ signal = reactive({ version: 0 });
21
+ metaSignals.set(key, signal);
22
+ }
23
+ return signal;
24
+ };
25
+ const notifyMeta = (key) => {
26
+ const signal = metaSignals.get(key);
27
+ if (signal) signal.version++;
28
+ };
8
29
  const ADD_META_RELATION_FIELDS = "ADD_META_RELATION_FIELDS";
9
30
  const ADD_META_VIEW_FIELDS = "ADD_META_VIEW_FIELDS";
10
31
  const LIMITS = {
@@ -12,9 +33,9 @@ const LIMITS = {
12
33
  metaRelationFields: { soft: 50, hard: 120 },
13
34
  metaViewFields: { soft: 50, hard: 200 }
14
35
  };
15
- const findMeta = (metas, matcher) => {
16
- for (const key in metas) {
17
- const item = metas[key];
36
+ const findMeta = (metas2, matcher) => {
37
+ for (const key in metas2) {
38
+ const item = metas2[key];
18
39
  if (item && matcher(item)) {
19
40
  touch("metas", key);
20
41
  return item;
@@ -46,7 +67,7 @@ const formatFields = (arr, flag = "") => {
46
67
  };
47
68
  const meta = {
48
69
  state: {
49
- metas: {},
70
+ metas,
50
71
  simpleMetas: {},
51
72
  metaStatus: {},
52
73
  simpleMetaStatus: {},
@@ -56,9 +77,24 @@ const meta = {
56
77
  metaViewFieldStatus: {}
57
78
  },
58
79
  mutations: {
80
+ // 逐 key 写入并只通知该 key(及其别名)的信号,不整体替换 metas
59
81
  [ADD_META](state, data) {
60
82
  touchKeys("metas", Object.keys(data));
61
- state.metas = prune("metas", { ...state.metas, ...data }, LIMITS.metas);
83
+ const metaAlias = aliasesOf(state);
84
+ for (const key in data) {
85
+ const meta2 = data[key];
86
+ state.metas[key] = meta2;
87
+ const aliases = [meta2.id, meta2.code, `${meta2.code}@${meta2.systemCode}@${meta2.clientCode}`];
88
+ aliases.forEach((alias) => {
89
+ if (alias && alias !== key) metaAlias[alias] = key;
90
+ });
91
+ notifyMeta(key);
92
+ aliases.forEach(notifyMeta);
93
+ }
94
+ const keep = prune("metas", { ...state.metas }, LIMITS.metas);
95
+ for (const key in state.metas) {
96
+ if (!hasOwn(keep, key)) delete state.metas[key];
97
+ }
62
98
  },
63
99
  [ADD_SIMPLE_META](state, data) {
64
100
  state.simpleMetas = { ...state.simpleMetas, ...data };
@@ -74,12 +110,18 @@ const meta = {
74
110
  },
75
111
  getters: {
76
112
  getMetaById: (state) => (id) => {
113
+ signalOf(id).version;
77
114
  const meta2 = state.metas[id];
78
115
  if (meta2) touch("metas", id);
79
116
  return meta2;
80
117
  },
81
118
  getMeta: (state) => (id) => {
82
- return findMeta(state.metas, (item) => item.id === id || item.code === id || item.code + "@" + item.systemCode + "@" + item.clientCode === id);
119
+ if (!id) return void 0;
120
+ signalOf(id).version;
121
+ const key = hasOwn(state.metas, id) ? id : aliasesOf(state)[id];
122
+ const meta2 = key ? state.metas[key] : void 0;
123
+ if (meta2) touch("metas", key);
124
+ return meta2;
83
125
  },
84
126
  getSimpleMeta: (state) => (id) => {
85
127
  return state.simpleMetas[id] || findMeta(state.metas, (item) => item.id === id || item.code === id);
@@ -45,16 +45,16 @@ const STYLE_AXES = [
45
45
  ]
46
46
  }
47
47
  ];
48
- function applyStylePreset(presetKey) {
48
+ function applyStylePreset(presetKey, settings = uiSettings) {
49
49
  const preset = STYLE_PRESETS.find((p) => p.key === presetKey);
50
50
  if (!preset) return;
51
51
  Object.keys(AXIS_ATTRS).forEach((k) => {
52
- uiSettings[k] = preset[k];
52
+ settings[k] = preset[k];
53
53
  });
54
54
  }
55
- function matchStylePreset() {
55
+ function matchStylePreset(settings = uiSettings) {
56
56
  const cur = {};
57
- for (const k of Object.keys(AXIS_ATTRS)) cur[k] = uiSettings[k];
57
+ for (const k of Object.keys(AXIS_ATTRS)) cur[k] = settings[k];
58
58
  const hit = STYLE_PRESETS.find((p) => Object.keys(AXIS_ATTRS).every((k) => cur[k] === p[k]));
59
59
  return hit ? hit.key : "";
60
60
  }
@@ -16,6 +16,8 @@ const DEFAULTS = {
16
16
  // 主题模式 light|dark|auto(auto 跟随系统 prefers-color-scheme)
17
17
  themeColor: "",
18
18
  // 主题色预设名(classicblue/purple/...;'' = 默认蓝,走既有 data-theme 兜底)
19
+ customTheme: null,
20
+ // 用户选择的自定义主题色快照 { code, color },刷新后也能在引导期恢复
19
21
  grayMode: false,
20
22
  // 灰色模式(全站置灰,哀悼日用)
21
23
  weakMode: false,
@@ -61,7 +63,7 @@ const DEFAULTS = {
61
63
  // 页签风格 smart|card|chrome(谷歌)
62
64
  tabsKeepAlive: true,
63
65
  // 页签缓存(会话级 keep-alive):开=切走保活/点回保持原样;关=切走即销毁/每次点开重新加载。不落盘不整组恢复
64
- settingsGroups: ["theme", "layout", "tabs", "page", "security"]
66
+ settingsGroups: ["theme", "style", "layout", "tabs", "page", "security"]
65
67
  // 设置抽屉折叠分组的展开态(记住用户的折叠选择,跨会话)
66
68
  };
67
69
  const NAV_MODES = [
@@ -142,16 +144,15 @@ if (!("isDark" in uiSettings)) {
142
144
  let _watching = false;
143
145
  let _suppressPersist = false;
144
146
  const _appDefaultVals = {};
145
- function persistDiff() {
147
+ function persistDiff(current, previous) {
146
148
  try {
147
- const out = {};
149
+ const out = JSON.parse(window.localStorage.getItem(KEY) || "{}") || {};
150
+ const next = JSON.parse(current);
151
+ const before = JSON.parse(previous);
148
152
  for (const k of Object.keys(DEFAULTS)) {
149
- if (k in _appDefaultVals && uiSettings[k] === _appDefaultVals[k]) continue;
150
- const cur = uiSettings[k];
151
- const def = DEFAULTS[k];
152
- const changed = Array.isArray(def) ? JSON.stringify(cur) !== JSON.stringify(def) : cur !== def;
153
- if (changed) out[k] = cur;
153
+ if (k !== "tabsKeepAlive" && JSON.stringify(next[k]) !== JSON.stringify(before[k])) out[k] = next[k];
154
154
  }
155
+ delete out.tabsKeepAlive;
155
156
  window.localStorage.setItem(KEY, JSON.stringify(out));
156
157
  } catch (e) {
157
158
  }
@@ -159,18 +160,46 @@ function persistDiff() {
159
160
  function persistUiSettings() {
160
161
  if (_watching) return;
161
162
  _watching = true;
162
- watch(uiSettings, () => {
163
+ watch(() => JSON.stringify(Object.fromEntries(Object.keys(DEFAULTS).map((k) => [k, uiSettings[k]]))), (current, previous) => {
163
164
  if (_suppressPersist) return;
164
- persistDiff();
165
- }, { deep: true, flush: "sync" });
165
+ persistDiff(current, previous);
166
+ }, { flush: "sync" });
166
167
  }
167
168
  persistUiSettings();
168
169
  let _mql = null;
169
170
  let _baseTheme = "";
171
+ function normalizeThemeColor(color) {
172
+ if (typeof color !== "string") return "";
173
+ const value = color.trim();
174
+ const hex = value.match(/^#([\da-f]{3}|[\da-f]{6})$/i);
175
+ if (hex) {
176
+ const full = hex[1].length === 3 ? [...hex[1]].map((c) => c + c).join("") : hex[1];
177
+ return [0, 2, 4].map((i) => parseInt(full.slice(i, i + 2), 16)).join(",");
178
+ }
179
+ const rgb = value.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i);
180
+ return rgb && rgb.slice(1).every((c) => Number(c) <= 255) ? rgb.slice(1).map(Number).join(",") : "";
181
+ }
170
182
  function applyTheme() {
183
+ var _a;
171
184
  const html = document.documentElement;
185
+ const preset = THEME_COLORS.find((c) => c.name === uiSettings.themeColor);
186
+ const custom = ((_a = uiSettings.customTheme) == null ? void 0 : _a.code) === uiSettings.themeColor ? uiSettings.customTheme : null;
187
+ const channels = normalizeThemeColor((preset == null ? void 0 : preset.color) || (custom == null ? void 0 : custom.color));
188
+ let colorStyle = document.getElementById("epx-ui-theme-color");
189
+ if (channels) {
190
+ html.style.removeProperty("--color-primary");
191
+ if (!colorStyle) {
192
+ colorStyle = document.createElement("style");
193
+ colorStyle.id = "epx-ui-theme-color";
194
+ document.head.appendChild(colorStyle);
195
+ }
196
+ const selector = `html[data-theme="${preset ? preset.name : "custom"}"]`;
197
+ colorStyle.textContent = `${selector}, ${selector} body { --color-primary: ${channels}; }`;
198
+ } else if (colorStyle) {
199
+ colorStyle.remove();
200
+ }
172
201
  if (uiSettings.themeColor) {
173
- html.setAttribute("data-theme", uiSettings.themeColor);
202
+ html.setAttribute("data-theme", custom && channels ? "custom" : uiSettings.themeColor);
174
203
  } else if (_baseTheme) {
175
204
  html.setAttribute("data-theme", _baseTheme);
176
205
  } else {
@@ -198,6 +227,7 @@ function initTheme() {
198
227
  watch(() => [
199
228
  uiSettings.themeMode,
200
229
  uiSettings.themeColor,
230
+ uiSettings.customTheme,
201
231
  uiSettings.grayMode,
202
232
  uiSettings.weakMode,
203
233
  uiSettings.styleRadius,
@@ -214,7 +244,8 @@ function initTheme() {
214
244
  else if (_mql.addListener) _mql.addListener(onChange);
215
245
  }
216
246
  }
217
- function applyAppDefaults(defaults = {}) {
247
+ function applyAppDefaults(defaults = {}, { replace = false } = {}) {
248
+ var _a;
218
249
  let userSet = {};
219
250
  try {
220
251
  userSet = JSON.parse(window.localStorage.getItem(KEY) || "{}") || {};
@@ -222,15 +253,25 @@ function applyAppDefaults(defaults = {}) {
222
253
  }
223
254
  _suppressPersist = true;
224
255
  try {
256
+ if (replace) {
257
+ Object.keys(_appDefaultVals).forEach((k) => {
258
+ delete _appDefaultVals[k];
259
+ });
260
+ _baseTheme = "";
261
+ (_a = document.getElementById("custom-theme")) == null ? void 0 : _a.remove();
262
+ document.documentElement.style.removeProperty("--color-primary");
263
+ }
225
264
  for (const k of Object.keys(DEFAULTS)) {
226
- if (defaults[k] !== void 0 && (userSet[k] === void 0 || userSet[k] === DEFAULTS[k])) {
227
- uiSettings[k] = defaults[k];
228
- _appDefaultVals[k] = defaults[k];
265
+ if (defaults[k] !== void 0) _appDefaultVals[k] = defaults[k];
266
+ if (replace || defaults[k] !== void 0) {
267
+ const value = userSet[k] !== void 0 ? userSet[k] : _appDefaultVals[k] ?? DEFAULTS[k];
268
+ uiSettings[k] = Array.isArray(value) ? [...value] : value;
229
269
  }
230
270
  }
231
271
  } finally {
232
272
  _suppressPersist = false;
233
273
  }
274
+ if (replace) applyTheme();
234
275
  }
235
276
  function migrateLegacyTheme(src) {
236
277
  if (!src) return "";
@@ -247,15 +288,44 @@ function migrateLegacyTheme(src) {
247
288
  return src;
248
289
  }
249
290
  function resetUiSettings() {
250
- Object.assign(uiSettings, DEFAULTS);
291
+ _suppressPersist = true;
292
+ Object.assign(uiSettings, DEFAULTS, { settingsGroups: [...DEFAULTS.settingsGroups] });
293
+ _suppressPersist = false;
294
+ Object.keys(_appDefaultVals).forEach((k) => {
295
+ delete _appDefaultVals[k];
296
+ });
297
+ try {
298
+ window.localStorage.removeItem(KEY);
299
+ } catch (e) {
300
+ }
251
301
  _baseTheme = "";
252
302
  try {
253
303
  const legacyKeys = ["epx_frame_theme", LEGACY_STYLE, LEGACY_PERSIST];
254
- legacyKeys.forEach((k) => window.localStorage.removeItem(k));
304
+ legacyKeys.forEach((k) => {
305
+ window.localStorage.removeItem(k);
306
+ window.sessionStorage.removeItem(k);
307
+ });
255
308
  } catch (e) {
256
309
  }
257
310
  const custom = document.getElementById("custom-theme");
258
311
  if (custom && custom.parentNode) custom.parentNode.removeChild(custom);
312
+ document.documentElement.style.removeProperty("--color-primary");
313
+ applyTheme();
314
+ }
315
+ function restoreAppUiDefaults() {
316
+ var _a;
317
+ const defaults = { ..._appDefaultVals };
318
+ const baseTheme = _baseTheme;
319
+ const legacyColor = (_a = document.getElementById("custom-theme")) == null ? void 0 : _a.textContent;
320
+ resetUiSettings();
321
+ applyAppDefaults(defaults, { replace: true });
322
+ _baseTheme = baseTheme;
323
+ if (legacyColor) {
324
+ const style = document.createElement("style");
325
+ style.id = "custom-theme";
326
+ style.textContent = legacyColor;
327
+ document.head.appendChild(style);
328
+ }
259
329
  applyTheme();
260
330
  }
261
331
  if (typeof window !== "undefined") window.__epxUiSettings = uiSettings;
@@ -273,7 +343,9 @@ export {
273
343
  initTheme,
274
344
  migrateLegacyTheme,
275
345
  navModeFlags,
346
+ normalizeThemeColor,
276
347
  persistUiSettings,
277
348
  resetUiSettings,
349
+ restoreAppUiDefaults,
278
350
  uiSettings
279
351
  };