/** 隔离渲染真实更正单组件,合成接口仅用于交互与布局验证。 */ 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 vue from '@vitejs/plugin-vue' import { createServer } from 'vite' const require = createRequire(import.meta.url) const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'C:/Users/Administrator/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright') const root = fileURLToPath(new URL('../', import.meta.url)) const output = path.resolve(root, '../.ai/correction-pairs-browser') fs.mkdirSync(output, { recursive: true }) const id = 'virtual:correction-pairs' const harness = ` import {createApp,h,ref} from 'vue' import {createPinia} from 'pinia' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import '/src/assets/styles/index.css' import Dialog from '/src/views/system/tplus/CorrectionPairDialog.vue' import {useUserStore} from '/src/store/user.ts' const pinia=createPinia() createApp({setup(){const open=ref(false);const user=useUserStore();user.userInfo={permissions:['tplus:facts']};return ()=>h('main',[ h('button',{onClick:()=>open.value=true},'打开更正处理'), h('button',{onClick:()=>user.userInfo={permissions:[]}},'切换只读'), h(Dialog,{modelValue:open.value,'onUpdate:modelValue':v=>open.value=v})])}}).use(pinia).use(ElementPlus).mount('#app')` const server = await createServer({ configFile: false, root, resolve: { alias: { '@': path.join(root, 'src') } }, define: { 'import.meta.env.VITE_API_BASE_URL': JSON.stringify('/api') }, cacheDir: path.join(output, 'vite-cache'), plugins: [vue(), { name: 'correction-pair-proof', resolveId: value => value === id ? '\0' + id : undefined, load: value => value === '\0' + id ? harness : undefined, configureServer(instance) { instance.middlewares.use('/__correction-proof', async (_request, response) => { response.setHeader('Content-Type', 'text/html; charset=utf-8') response.end(await instance.transformIndexHtml('/__correction-proof', '
')) }) }, }], server: { host: '127.0.0.1', port: 0 }, optimizeDeps: { include: ['vue', 'element-plus', 'pinia'] } }) let browser const errors = [], saves = [], checks = [] try { await server.listen() browser = await chromium.launch({ headless: true, channel: 'msedge' }) const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, reducedMotion: 'reduce' }) page.on('pageerror', error => errors.push(error.message)) await page.route(url => url.pathname.startsWith('/api/'), async route => { const request = route.request(), url = new URL(request.url()) let data if (url.pathname.endsWith('/correction-pairs')) { saves.push(request.postDataJSON()); data = { pair_key: 'synthetic' } } else if (url.pathname.endsWith('/facts')) data = { list: [{ fact_key: 'original', data: { customer_id: '8' } }], total: 1 } else if (url.pathname.endsWith('/source-records')) data = { list: url.searchParams.get('entity') === 'receipts' ? [{ source_id: '30', source_no: 'B-CASH', date: '2026-03-10' }] : [{ source_id: '30', source_no: 'A-NEG', date: '2026-03-10' }, { source_id: '40', source_no: 'B-POS', date: '2026-03-10' }], total: 2 } else throw new Error('未预期接口:' + url.pathname) return route.fulfill({ json: { code: 200, message: 'success', data } }) }) await page.goto(`http://127.0.0.1:${server.httpServer.address().port}/__correction-proof`) await page.getByRole('button', { name: '打开更正处理' }).click() const dialog = page.getByRole('dialog', { name: '转客户更正单处理' }) await dialog.waitFor() await dialog.getByRole('button', { name: '确认成对处理' }).click() assert.equal(saves.length, 0) checks.push('必填资料不完整时阻止提交') for (const width of [1440, 768, 390]) { await page.setViewportSize({ width, height: 900 }) await page.screenshot({ path: path.join(output, `pair-${width}.png`), fullPage: true }) assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false) } checks.push('1440、768、390 宽度无页面横向溢出') await page.setViewportSize({ width: 1440, height: 900 }) for (const [label, option] of [['负数更正单', 'A-NEG'], ['正数更正单', 'B-POS'], ['原业务平账组', 'original'], ['实际回款', 'B-CASH']]) { await dialog.getByRole('combobox', { name: label, exact: true }).locator('xpath=ancestor::div[contains(@class,"el-select__wrapper")][1]').click() await page.getByRole('option').filter({ hasText: option }).click() await page.keyboard.press('Escape') } await dialog.getByRole('textbox', { name: '更正处理依据' }).fill('合成转客户例单已核对') await dialog.getByRole('button', { name: '确认成对处理' }).click() await dialog.waitFor({ state: 'hidden' }) assert.deepEqual(saves[0], { negative_shipment_id: '30', positive_shipment_id: '40', receipt_ids: ['30'], target_group_key: 'original', evidence: '合成转客户例单已核对' }) checks.push('成对选单、同 ID 不同实体、实际回款和原业务完整提交') await page.getByRole('button', { name: '切换只读' }).click() await page.getByRole('button', { name: '打开更正处理' }).click() await dialog.waitFor() assert.equal(await dialog.getByRole('button', { name: '确认成对处理' }).isDisabled(), true) checks.push('无事实维护权限时禁用提交') assert.deepEqual(errors, []) fs.writeFileSync(path.join(output, 'result.json'), JSON.stringify({ checks, errors }, null, 2)) console.log(JSON.stringify({ checks, errors })) } finally { await browser?.close(); await server.close() }