From ec8ff4f6259404460a0b4437e29a40986c2c4b9d Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 12 十月 2026 18:31:43 +0800
Subject: [PATCH] feat(workflow): 集成自动化审批功能并优化多个业务模块

---
 scripts/check-analytics-permissions.cjs |   62 +++++++++++++++++++++++++++++++
 1 files changed, 62 insertions(+), 0 deletions(-)

diff --git a/scripts/check-analytics-permissions.cjs b/scripts/check-analytics-permissions.cjs
new file mode 100644
index 0000000..5295a60
--- /dev/null
+++ b/scripts/check-analytics-permissions.cjs
@@ -0,0 +1,62 @@
+const fs = require('node:fs');
+const path = require('node:path');
+const ts = require('typescript');
+const assert = require('node:assert/strict');
+const { parse, compileTemplate } = require('vue/compiler-sfc');
+const root = path.join(__dirname, '../src/views/dashboard/analytics');
+function evaluate(source, mocks) {
+  const js = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText;
+  const module = { exports: {} };
+  new Function('require', 'module', 'exports', 'defineOptions', 'defineProps', 'defineEmits', js)(
+    (id) => mocks[id] || {}, module, module.exports, () => {}, () => ({ repairActiveCount: 0 }), () => () => {});
+  return module.exports;
+}
+const vue = { computed: (getter) => ({ get value() { return getter(); } }), ref: (value) => ({ value }) };
+async function scenario(codes, names, expected) {
+  const store = { accessRoutes: [{ name: 'Parent', children: names.map((name) => ({ name })) }] };
+  const home = evaluate(fs.readFileSync(path.join(root, 'use-home-access.ts'), 'utf8'), {
+    vue, '@vben/stores': { useAccessStore: () => store },
+    '@vben/access': { useAccess: () => ({ hasAccessByCodes: (requested) => requested.some((code) => codes.includes(code)) }) },
+  });
+  const calls = [];
+  const mocks = { '../use-home-access': home, './use-home-access': home,
+    'vue-router': { useRouter: () => ({ push: () => {} }) }, './data': { defaultSummary: {} } };
+  for (const [id, method] of [
+    ['#/api/mes/home', 'getHomeSummary'], ['#/api/mes/home', 'getProductionTrend'],
+    ['#/api/mes/home', 'getWorkOrderStatusDistribution'], ['#/api/erp/purchase/order', 'getPurchaseOrderPage'],
+    ['#/api/erp/sale/order', 'getSaleOrderPage'], ['#/api/mes/wm/materialstock', 'getMaterialStockPage'],
+    ['#/api/bpm/task', 'getTaskTodoPage'],
+  ]) {
+    mocks[id] ||= {};
+    mocks[id][method] = async () => { calls.push(method); return method.startsWith('getHome') ? {} : method.endsWith('Page') ? { total: 3 } : []; };
+  }
+  mocks['@vben/plugins/echarts'] = { useEcharts: () => ({ renderEcharts: () => {} }) };
+  for (const file of ['index.vue', 'modules/biz-cards.vue', 'modules/production-trend.vue', 'modules/alert-panel.vue']) {
+    const mounted = [];
+    mocks.vue = { ...vue, onMounted: (fn) => mounted.push(fn) };
+    const { descriptor } = parse(fs.readFileSync(path.join(root, file), 'utf8'));
+    evaluate(descriptor.scriptSetup.content, mocks);
+    mounted.forEach((fn) => fn());
+  }
+  await new Promise((resolve) => setImmediate(resolve));
+  assert.deepEqual(calls.sort(), expected.sort());
+  const access = home.useHomeAccess();
+  store.accessRoutes = [{ name: 'Forbidden', meta: { menuVisibleWithForbidden: true }, children: [{ name: 'Child' }] }];
+  assert.equal(access.canNavigate('Forbidden'), false);
+  assert.equal(access.canNavigate('Child'), false);
+}
+(async () => {
+  await scenario([], [], []);
+  await scenario(['erp:purchase-order:query'], ['ErpPurchaseOrder'], ['getPurchaseOrderPage']);
+  await scenario(['erp:purchase-order:query'], [], []);
+  await scenario(['mes:home:query'], [], ['getHomeSummary', 'getProductionTrend', 'getWorkOrderStatusDistribution']);
+  await scenario(['*:*:*'], ['ErpPurchaseOrder', 'ErpSaleOrder', 'MesWmMaterialStock', 'BpmTaskTodo'],
+    ['getHomeSummary', 'getProductionTrend', 'getWorkOrderStatusDistribution', 'getPurchaseOrderPage', 'getSaleOrderPage', 'getMaterialStockPage', 'getTaskTodoPage']);
+  for (const file of ['index.vue', ...fs.readdirSync(path.join(root, 'modules')).filter((f) => f.endsWith('.vue')).map((f) => `modules/${f}`)]) {
+    const { descriptor, errors } = parse(fs.readFileSync(path.join(root, file), 'utf8'));
+    assert.equal(errors.length, 0, file);
+    const result = compileTemplate({ source: descriptor.template.content, filename: file, id: file });
+    assert.equal(result.errors.length, 0, `${file}: ${result.errors}`);
+  }
+  console.log('PASS: 5 permission scenarios, forbidden routes, and all analytics Vue templates');
+})();

--
Gitblit v1.9.3