From a2d9fcb259621829bf4ec9680b5dbeac95ab5805 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 15 十月 2026 16:19:54 +0800
Subject: [PATCH] fix(crm): 销售目标达成分析默认查当前季度
---
scripts/one-click-schedule-qa.mjs | 85 ++++++++++++++++++++++++++++++++++++++++++
1 files changed, 85 insertions(+), 0 deletions(-)
diff --git a/scripts/one-click-schedule-qa.mjs b/scripts/one-click-schedule-qa.mjs
new file mode 100644
index 0000000..91a1a07
--- /dev/null
+++ b/scripts/one-click-schedule-qa.mjs
@@ -0,0 +1,85 @@
+// Component interaction check with simulated API data; never creates production tasks.
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import path from 'node:path';
+import { createRequire } from 'node:module';
+import { fileURLToPath } from 'node:url';
+import { createServer } from 'vite';
+
+const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+const require = createRequire(path.join(root, 'package.json'));
+const { parse, compileScript } = require('vue/compiler-sfc');
+const ts = require('typescript');
+const { chromium } = require('C:/Users/浜�/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright');
+const output = path.resolve(root, '../scripts/one-click-schedule-20261014');
+fs.mkdirSync(output, { recursive: true });
+const modal = `import {defineComponent,h,reactive,onMounted} from 'vue';
+export function useVbenModal(options){const state=reactive({open:true,locked:false,confirmDisabled:true,...options});
+const api={getData:()=>({workOrderIds:[1,2]}),setState:v=>Object.assign(state,v),lock:()=>state.locked=true,unlock:()=>state.locked=false,close:async()=>{if(options.onBeforeClose&&!options.onBeforeClose())return;state.open=false;await options.onOpenChange(false)}};
+window.qaModal={state,api,options};onMounted(()=>options.onOpenChange(true));
+const Modal=defineComponent({props:['title'],setup(props,{slots}){return()=>state.open?h('section',{class:'qa-modal'},[
+h('header',props.title),h('div',{class:'qa-content'},slots.default?.()),h('footer',[
+h('button',{class:'qa-close',disabled:state.locked,onClick:()=>api.close()},'鍙栨秷'),
+h('button',{class:'qa-confirm',disabled:state.locked||state.confirmDisabled,onClick:()=>options.onConfirm()},state.locked?'姝e湪鎺掍骇鈥�':state.confirmText)])]):null}});return[Modal,api]}`;
+const api = `export async function previewBatchSchedule(request){window.qa.previewCalls++;await new Promise(r=>setTimeout(r,120));
+const blocked=window.qa.mode==='blocked',late=window.qa.mode==='late';return {canExecute:!blocked,previewHash:'test-hash',previewTime:'2026-10-14 15:00:00',warnings:late?['宸ュ崟WO-001棰勮瀹屾垚鏃堕棿鏅氫簬浜ゆ湡']:[],
+orders:[{workOrderId:1,workOrderCode:'WO-001',quantity:100,taskCount:blocked?0:2,requestDate:'2026-10-16 00:00:00',endTime:'2026-10-16 12:00:00',late,error:blocked?'WO-001锛氬伐搴忔垚鍨嬫病鏈夊彲鐢ㄥ伐浣滅珯鍙婃湁鏁堣澶囦骇鑳斤紝璇峰厛缁存姢':undefined},{workOrderId:2,workOrderCode:'WO-002',quantity:200,taskCount:blocked?0:2,requestDate:'2026-10-19 00:00:00',endTime:'2026-10-19 13:00:00',late:false,error:blocked?'WO-002锛氬伐搴忓寘瑁呯己灏戣澶囦骇鑳斤紝璇峰厛缁存姢':undefined}],
+tasks:blocked?[]:[{workOrderId:1,workOrderCode:'WO-001',processId:1,processName:'鎴愬瀷',workstationName:'鎴愬瀷宸ヤ綔绔橝',machineryNames:'鎴愬瀷璁惧A01',quantity:100,hourlyCapacity:50,duration:2,startTime:'2026-10-16 09:00:00',endTime:'2026-10-16 11:00:00'},{workOrderId:1,workOrderCode:'WO-001',processId:2,processName:'鍖呰',workstationName:'鍖呰宸ヤ綔绔橞',machineryNames:'鍖呰璁惧B01',quantity:100,hourlyCapacity:100,duration:1,startTime:'2026-10-16 11:00:00',endTime:'2026-10-16 12:00:00'},{workOrderId:2,workOrderCode:'WO-002',processId:1,processName:'鎴愬瀷',workstationName:'鎴愬瀷宸ヤ綔绔橝',machineryNames:'鎴愬瀷璁惧A01',quantity:200,hourlyCapacity:100,duration:2,startTime:'2026-10-19 09:00:00',endTime:'2026-10-19 11:00:00'},{workOrderId:2,workOrderCode:'WO-002',processId:2,processName:'鍖呰',workstationName:'鍖呰宸ヤ綔绔橞',machineryNames:'鍖呰璁惧B01',quantity:200,hourlyCapacity:100,duration:2,startTime:'2026-10-19 11:00:00',endTime:'2026-10-19 13:00:00'}]};}
+export async function executeBatchSchedule(request){window.qa.executeCalls++;window.qa.lastRequest=request;await new Promise(r=>setTimeout(r,200));if(window.qa.mode==='executeError')throw new Error('鎺掍骇鏁版嵁宸插彉鍖栵紝璇烽噸鏂扮敓鎴愰瑙堝悗纭');return{taskIds:[1,2,3,4]}}`;
+const html = `<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>涓�閿帓浜т氦浜掗獙璇�</title>
+<style>body{margin:0;background:#f4f6f8;font-family:Microsoft YaHei,sans-serif;color:#172033}.qa-modal{width:calc(100% - 32px);max-width:1200px;margin:20px auto;background:white;border-radius:8px}.qa-modal header{padding:18px 24px;font-size:18px;font-weight:600;border-bottom:1px solid #e8ebee}.qa-content{padding:24px}.qa-modal footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid #e8ebee}.qa-modal button{padding:8px 16px;border:1px solid #d9d9d9;border-radius:6px;background:white;cursor:pointer}.qa-modal .qa-confirm{background:#1677ff;color:white;border-color:#1677ff}.qa-modal button:disabled{color:#8c8c8c;background:#f5f5f5;border-color:#d9d9d9;cursor:default}.space-y-5>*+*{margin-top:20px}.flex{display:flex}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-end{align-items:flex-end}.gap-3{gap:12px}.gap-2{gap:8px}.font-medium{font-weight:500}.text-sm{font-size:14px}.text-muted-foreground{color:#657083}.text-destructive{color:#cf1322}.mb-2{margin-bottom:8px}.mt-2{margin-top:8px}h3{font-size:15px;margin-top:0}p{margin:4px 0}@media(max-width:600px){.qa-content{padding:16px}.qa-modal header{padding:16px}.ant-descriptions{overflow-x:auto}}</style>
+<div id="app"></div><script type="module" src="/qa-entry.js"></script></html>`;
+const entry = `import{createApp,h}from'vue';import AutoSchedule from'/src/views/mes/pro/task/modules/auto-schedule.vue';window.qa={mode:new URLSearchParams(location.search).get('mode')||'valid',previewCalls:0,executeCalls:0,success:0};createApp({render:()=>h(AutoSchedule,{onSuccess:()=>window.qa.success++})}).mount('#app')`;
+
+const server = await createServer({ root, configFile: false, appType: 'custom', cacheDir: 'node_modules/.cache/one-click-schedule-qa', server: { host: '127.0.0.1', port: 0 },
+ plugins: [{ name: 'schedule-qa', enforce: 'pre',
+ resolveId(id) { if (id === '@vben/common-ui') return '\0qa-modal'; if (id === '#/api/mes/pro/task') return '\0qa-api'; if (id === '/qa-entry.js') return '\0qa-entry'; },
+ load(id) { if (id === '\0qa-modal') return modal; if (id === '\0qa-api') return api; if (id === '\0qa-entry') return entry; },
+ transform(code, id) { if (!id.endsWith('.vue')) return;
+ const { descriptor } = parse(code); const script = compileScript(descriptor, { id: 'schedule-qa', inlineTemplate: true });
+ return { code: ts.transpileModule(script.content, { compilerOptions: { target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.ESNext } }).outputText, map: null };
+ },
+ configureServer(server) { server.middlewares.use((request,response,next)=>{if(request.url?.startsWith('/?')||request.url==='/'){response.setHeader('Content-Type','text/html;charset=utf-8');response.end(html)}else next()}); },
+ }], optimizeDeps: { entries: [], noDiscovery: true, include: ['vue', 'ant-design-vue', 'dayjs'] } });
+await server.listen();
+let browser;
+const errors=[];
+try {
+ browser=await chromium.launch({headless:true,executablePath:'C:/Program Files/Google/Chrome/Application/chrome.exe'});
+ const page=await browser.newPage({viewport:{width:1440,height:1000}});
+ page.on('pageerror',error=>errors.push(error.message));
+ const url=`http://127.0.0.1:${server.httpServer.address().port}`;
+ await page.goto(url);await page.getByText('宸ュ崟鏍¢獙',{exact:true}).waitFor();
+ assert.equal(await page.locator('.qa-confirm').isEnabled(),true);
+ assert.equal(await page.evaluate(()=>window.qa.previewCalls),1,'Opening modal must generate exactly one preview');
+ await page.screenshot({path:path.join(output,'desktop-preview.png'),fullPage:true});
+ await page.locator('.ant-picker').click();
+ await page.locator('td[title="2026-10-20"]').click();
+ await page.locator('.ant-picker-ok button').click();
+ await page.waitForTimeout(150);
+ assert.equal(await page.locator('.qa-confirm').isEnabled(),false,'Changed earliest time must invalidate preview');
+ await page.getByText('鐢熸垚鎺掍骇棰勮',{exact:true}).click();await page.getByText('宸ュ崟鏍¢獙',{exact:true}).waitFor();
+ await page.locator('.qa-confirm').click();
+ assert.equal(await page.locator('.qa-close').isEnabled(),false,'Cancel must be disabled while executing');
+ await page.waitForFunction(()=>window.qa.success===1);
+ assert.equal(await page.evaluate(()=>window.qa.executeCalls),1,'Confirm must execute only once');
+ await page.goto(url+'/?mode=blocked');await page.getByText('宸ュ崟鏍¢獙',{exact:true}).waitFor();
+ assert.equal(await page.locator('.qa-confirm').isEnabled(),false,'Configuration errors must block submission');
+ await page.setViewportSize({width:390,height:844});await page.screenshot({path:path.join(output,'mobile-blocked.png'),fullPage:true});
+ await page.goto(url+'/?mode=late');await page.getByText('宸ュ崟鏍¢獙',{exact:true}).waitFor();
+ assert.equal(await page.locator('.qa-confirm').isEnabled(),false,'Late risk requires acknowledgement');
+ await page.getByText('鎴戝凡鏍稿骞舵帴鍙椾互涓婁氦鏈熷欢鏈熼闄�',{exact:true}).click();
+ assert.equal(await page.locator('.qa-confirm').isEnabled(),true);
+ await page.locator('.qa-confirm').click();await page.waitForFunction(()=>window.qa.success===1);
+ assert.equal(await page.evaluate(()=>window.qa.lastRequest.allowLate),true);
+ await page.goto(url+'/?mode=executeError');await page.getByText('宸ュ崟鏍¢獙',{exact:true}).waitFor();
+ await page.locator('.qa-confirm').click();await page.getByText('鎺掍骇鏁版嵁宸插彉鍖栵紝璇烽噸鏂扮敓鎴愰瑙堝悗纭',{exact:true}).waitFor();
+ assert.equal(await page.locator('.qa-confirm').isEnabled(),false,'Failed execution must discard old preview');
+ assert.equal(await page.locator('.qa-close').isEnabled(),true,'Failure must unlock modal');
+ assert.deepEqual(errors,[]);
+ const result={passed:true,scope:'Actual scheduling component, simulated APIs and modal adapter; no production writes',checks:['initial preview','time change invalidates preview','single submit','execution locks cancel','missing config blocks','late acknowledgement','server rejection unlocks and invalidates'],screenshots:['desktop-preview.png','mobile-blocked.png']};
+ fs.writeFileSync(path.join(output,'frontend-result.json'),JSON.stringify(result,null,2));console.log(JSON.stringify(result));
+} catch (error) {
+ console.error(JSON.stringify({message:error.message,browserErrors:errors}));
+ throw error;
+} finally { await browser?.close();await server.close(); }
--
Gitblit v1.9.3