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

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 (101) hide show
  1. package/API.md +26 -25
  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 +4 -0
  5. package/dist/.cache/route-chart-release/frontend/frame/public/js/echarts.min.js +4 -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/ecStat.min-BrTYPOXX.js +1 -0
  9. package/dist/assets/echarts-wordcloud.min-nWR4Hzny.js +3 -0
  10. package/dist/assets/echarts.min-DROS9E5X.js +45 -0
  11. package/dist/assets/role_select-DTqRlFNz.webp +0 -0
  12. package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/jsencrypt@3.5.4/_vendor/jsencrypt/bin/jsencrypt.min.js +1 -1
  13. package/dist/src/RootApp.js +54 -0
  14. package/dist/src/api/createHttp.js +6 -0
  15. package/dist/src/app-ui-settings.js +49 -0
  16. package/dist/src/axios.js +10 -15
  17. package/dist/src/chart-runtime-assets.js +9 -0
  18. package/dist/src/create-app.js +3 -6
  19. package/dist/src/index.js +2 -0
  20. package/dist/src/loadComponent.js +2 -2
  21. package/dist/src/menu-route.js +11 -0
  22. package/dist/src/mixins/component.js +3 -0
  23. package/dist/src/navigation-context.js +47 -0
  24. package/dist/src/navigation.js +43 -12
  25. package/dist/src/package-install.js +3 -2
  26. package/dist/src/plugins/element-plus.js +8 -8
  27. package/dist/src/public-api.js +4 -2
  28. package/dist/src/route-components.js +9 -0
  29. package/dist/src/router/config.js +7 -8
  30. package/dist/src/runtime-services.js +1 -1
  31. package/dist/src/runtime-utils/meta.js +3 -0
  32. package/dist/src/runtime-utils/pdf.viewer.js +31 -15
  33. package/dist/src/runtime-utils/util.js +29 -5
  34. package/dist/src/shared-packages.js +1 -3
  35. package/dist/src/store/modules/list.js +25 -3
  36. package/dist/src/store/modules/meta.js +49 -7
  37. package/dist/src/style-presets.js +4 -4
  38. package/dist/src/ui-settings.js +90 -18
  39. package/dist/src/views/App.js +48 -0
  40. package/dist/src/views/AppRoleSelect.js +233 -0
  41. package/dist/src/views/Error.js +210 -0
  42. package/dist/src/views/Module.js +173 -0
  43. package/dist/style.css +251 -0
  44. package/package.json +4 -4
  45. package/src/RootApp.vue +75 -0
  46. package/src/api/createHttp.js +7 -0
  47. package/src/api/index.js +47 -47
  48. package/src/app-ui-settings.js +31 -0
  49. package/src/axios.js +311 -318
  50. package/src/chart-runtime-assets.js +7 -0
  51. package/src/create-app.js +3 -7
  52. package/src/index.js +28 -28
  53. package/src/install.js +157 -157
  54. package/src/loadComponent.js +187 -187
  55. package/src/loadLibrary.js +99 -99
  56. package/src/menu-route.js +12 -1
  57. package/src/mixins/component.js +1464 -1461
  58. package/src/navigation-context.js +49 -0
  59. package/src/navigation.js +208 -176
  60. package/src/package-install.js +2 -1
  61. package/src/plugins/element-plus.js +302 -302
  62. package/src/plugins/frame.js +178 -178
  63. package/src/public-api.js +1 -0
  64. package/src/route-components.js +7 -0
  65. package/src/router/config.js +861 -864
  66. package/src/router/public.js +1 -1
  67. package/src/runtime-services.js +1 -1
  68. package/src/runtime-utils/fieldFactory.js +179 -179
  69. package/src/runtime-utils/meta.js +399 -378
  70. package/src/runtime-utils/pdf.viewer.js +672 -644
  71. package/src/runtime-utils/util.js +1357 -1330
  72. package/src/shared-packages.js +1 -3
  73. package/src/store/modules/config.js +351 -351
  74. package/src/store/modules/list.js +30 -3
  75. package/src/store/modules/locale.js +236 -236
  76. package/src/store/modules/meta.js +314 -265
  77. package/src/style-presets.js +4 -4
  78. package/src/ui-settings.js +84 -23
  79. package/src/views/App.vue +53 -0
  80. package/src/views/AppRoleSelect.vue +334 -0
  81. package/src/views/Error.vue +63 -0
  82. package/src/views/Module.vue +210 -0
  83. package/src/xss.js +26 -26
  84. package/types/app.d.ts +10 -10
  85. package/types/axios.d.ts +1 -1
  86. package/types/component-mixin.d.ts +1 -1
  87. package/types/field-factory.d.ts +1 -1
  88. package/types/global-mixin.d.ts +1 -1
  89. package/types/http.d.ts +1 -1
  90. package/types/index.d.ts +3 -0
  91. package/types/meta.d.ts +1 -1
  92. package/types/navigation.d.ts +21 -21
  93. package/types/pdf-viewer.d.ts +1 -1
  94. package/types/runtime.d.ts +13 -13
  95. package/types/store.d.ts +1 -1
  96. package/types/util.d.ts +2 -2
  97. /package/dist/{build → .cache/route-chart-release/frontend/frame/build}/stubs/empty-components.js +0 -0
  98. /package/dist/{src → .cache/route-chart-release/frontend/frame/src}/element-icons.names.js +0 -0
  99. /package/dist/{src → .cache/route-chart-release/frontend/frame/src}/element-plus.lazy.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/en.js +0 -0
  101. /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,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,8 @@ 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 chartRuntimeAssets from "../chart-runtime-assets.js";
11
+ import wordcloudUrl from "../../.cache/route-chart-release/frontend/frame/public/js/echarts-wordcloud.min.js";
10
12
  const installingLibraries = /* @__PURE__ */ new Map();
11
13
  class Util {
12
14
  constructor() {
@@ -585,7 +587,8 @@ class Util {
585
587
  }
586
588
  script.onerror = function() {
587
589
  console.error("[util.loadJS] 加载失败:" + url);
588
- callback();
590
+ if (options.onerror) options.onerror(new Error("脚本加载失败:" + url));
591
+ else callback();
589
592
  };
590
593
  script.src = url;
591
594
  document.getElementsByTagName("head")[0].appendChild(script);
@@ -788,7 +791,7 @@ class Util {
788
791
  const publicPath = this.getPublicPath();
789
792
  const jsDir = this.getStaticJsDir();
790
793
  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);
794
+ 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
795
  this.loadPlugins[plugin] = loadPromise;
793
796
  return loadPromise;
794
797
  } else if (plugin === "BMap") {
@@ -828,7 +831,26 @@ class Util {
828
831
  this.loadPlugins[plugin] = loadPromise;
829
832
  return loadPromise;
830
833
  } 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);
834
+ const files = window.STATIC_JS_DIR ? [publicPath + jsDir + "echarts.min.js?v=6.0.0-full", publicPath + jsDir + "ecStat.min.js"] : chartRuntimeAssets;
835
+ const loadPromise = Promise.all(files.map((url) => new Promise((resolve, reject) => {
836
+ this.loadJS({ url, callback: resolve, onerror: reject });
837
+ }))).then(() => {
838
+ if (!globalThis.echarts || !globalThis.ecStat) throw new Error("图表资源加载失败,请检查 ECharts 和 ecStat 脚本");
839
+ return globalThis.echarts;
840
+ }).catch((error) => {
841
+ delete this.loadPlugins[plugin];
842
+ throw error;
843
+ });
844
+ this.loadPlugins[plugin] = loadPromise;
845
+ return loadPromise;
846
+ } else if (plugin === "echarts-wordcloud") {
847
+ const url = window.STATIC_JS_DIR ? publicPath + jsDir + "echarts-wordcloud.min.js" : wordcloudUrl;
848
+ const loadPromise = new Promise((resolve, reject) => {
849
+ this.loadJS({ url, callback: resolve, onerror: reject });
850
+ }).catch((error) => {
851
+ delete this.loadPlugins[plugin];
852
+ throw error;
853
+ });
832
854
  this.loadPlugins[plugin] = loadPromise;
833
855
  return loadPromise;
834
856
  } else if (plugin === "pdf") {
@@ -1054,7 +1076,8 @@ class Util {
1054
1076
  }
1055
1077
  encrypt(content, key, { type } = {}) {
1056
1078
  if (type === "aes") {
1057
- return __epxPackagePreload(() => import("crypto-js"), true ? [] : void 0, import.meta.url).then((CryptoJS) => {
1079
+ return __epxPackagePreload(() => import("crypto-js"), true ? [] : void 0, import.meta.url).then((cryptoModule) => {
1080
+ const CryptoJS = "default" in cryptoModule ? cryptoModule.default : cryptoModule;
1058
1081
  const base64Key = atob(key);
1059
1082
  const aesKey = CryptoJS.enc.Utf8.parse(base64Key.toString(CryptoJS.enc.Utf8));
1060
1083
  const encrypted = CryptoJS.AES.encrypt(content, aesKey, {
@@ -1074,7 +1097,8 @@ class Util {
1074
1097
  }
1075
1098
  decrypt(content, { type, key }) {
1076
1099
  if (type === "aes") {
1077
- return __epxPackagePreload(() => import("crypto-js"), true ? [] : void 0, import.meta.url).then((CryptoJS) => {
1100
+ return __epxPackagePreload(() => import("crypto-js"), true ? [] : void 0, import.meta.url).then((cryptoModule) => {
1101
+ const CryptoJS = "default" in cryptoModule ? cryptoModule.default : cryptoModule;
1078
1102
  const base64Key = atob(key);
1079
1103
  const aesKey = CryptoJS.enc.Utf8.parse(base64Key.toString(CryptoJS.enc.Utf8));
1080
1104
  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
  };
@@ -0,0 +1,48 @@
1
+ import '../../style.css';
2
+ import { openBlock, createBlock, resolveDynamicComponent, createCommentVNode } from "vue";
3
+ import _export_sfc from "../../_virtual/_plugin-vue_export-helper.js";
4
+ const _sfc_main = {
5
+ name: "epx-app",
6
+ computed: {
7
+ appConfig() {
8
+ return this.$store.state.config.appConfig;
9
+ }
10
+ },
11
+ data() {
12
+ return {
13
+ load: false,
14
+ component: "epx-app-index",
15
+ firstPage: {}
16
+ };
17
+ },
18
+ methods: {
19
+ getPage() {
20
+ const { FIRST_PAGE, MOBILE_FIRST_PAGE } = this.appConfig;
21
+ this.$store.dispatch({
22
+ // 加回调
23
+ type: "ADD_PAGE",
24
+ pageId: this.$isMobile ? MOBILE_FIRST_PAGE : FIRST_PAGE
25
+ }).then((page = {}) => {
26
+ const { pagePath } = page;
27
+ this.firstPage = page;
28
+ this.component = this.$loadComponent(pagePath, this.component);
29
+ this.load = true;
30
+ }).catch(() => {
31
+ this.load = true;
32
+ });
33
+ }
34
+ },
35
+ created() {
36
+ this.getPage();
37
+ }
38
+ };
39
+ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
40
+ return $data.load ? (openBlock(), createBlock(resolveDynamicComponent($data.component), {
41
+ key: 0,
42
+ page: $data.firstPage
43
+ }, null, 8, ["page"])) : createCommentVNode("", true);
44
+ }
45
+ const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
46
+ export {
47
+ App as default
48
+ };