import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { test } from 'node:test' import vm from 'node:vm' import * as Vue from 'vue' import { parse } from '@vue/compiler-sfc' import { compile } from '@vue/compiler-dom' import { renderToString } from '@vue/server-renderer' const reports = { palletInventory: 'listPalletInventory', consumption: 'listConsumptionReport', return: 'listReturnReport', palletTrace: 'listPalletTrace' } async function loadPage(resource, rows = []) { const source = await readFile(new URL(`../src/views/report/${resource}/index.vue`, import.meta.url), 'utf8') const { descriptor } = parse(source) const requests = [] const context = vm.createContext({ ref: Vue.ref, computed: Vue.computed, getCurrentInstance: () => ({ proxy: { addDateRange: query => query, resetForm() {} } }), onBeforeUnmount() {}, [reports[resource]]: async query => { requests.push({ ...query }) return { rows, total: rows.length } } }) // 执行真实页面脚本,仅替换网络和组件生命周期边界。 vm.runInContext(`${descriptor.scriptSetup.content.replace(/^import .*$/gm, '')} globalThis.page = { queryParams, reportList, showSearch, loading, loadError, total, dateRange, handleQuery, resetQuery, getList, handleSizeChange, handleCurrentChange, ${resource === 'palletInventory' ? 'flowStateOptions, flowStateLabel, currentLocation, idFallback,' : ''} ${resource === 'palletTrace' ? 'eventTypeOptions, eventTypeLabel, sourceLabel, flowStateLabel, formatLocation,' : ''} ...(typeof traceSummary === 'undefined' ? {} : { traceSummary }) }`, context) await new Promise(resolve => setImmediate(resolve)) const fields = new Map() const render = new Function('Vue', compile(descriptor.template.content, { mode: 'function', prefixIdentifiers: true }).code)(Vue) async function html() { const app = Vue.createSSRApp({ setup: () => ({ ...context.page, parseTime: value => value || '' }), render }) app.directive('hasPermi', { getSSRProps: () => ({}) }) app.directive('loading', { getSSRProps: () => ({}) }) const box = { setup: (_, { slots }) => () => Vue.h('div', slots.default?.()) } for (const name of ['el-form', 'el-row', 'el-select', 'el-option', 'el-button', 'right-toolbar', 'pagination', 'el-date-picker', 'el-empty']) { app.component(name, box) } app.component('el-form-item', { props: ['prop', 'label'], setup(props, { slots }) { Vue.provide('reportField', props.prop) return () => Vue.h('label', { 'data-field': props.prop }, [props.label, slots.default?.()]) } }) const input = { props: ['modelValue'], setup(props, { attrs }) { fields.set(Vue.inject('reportField'), value => attrs['onUpdate:modelValue'](value)) return () => Vue.h('input', { value: props.modelValue }) } } app.component('el-input', input) app.component('el-input-number', input) app.component('el-alert', { props: ['title'], setup: props => () => Vue.h('p', props.title) }) app.component('el-table', { props: ['data'], setup(props, { slots }) { Vue.provide('reportRows', props.data) return () => Vue.h('div', slots.default?.()) } }) app.component('el-table-column', { props: ['prop', 'label'], setup(props, { slots }) { const data = Vue.inject('reportRows') return () => Vue.h('section', { 'data-column': props.prop, 'aria-label': props.label }, data.map(row => Vue.h('span', slots.default ? slots.default({ row }) : String(row[props.prop] ?? '')))) } }) return renderToString(app) } return { page: context.page, requests, fields, html } } for (const resource of Object.keys(reports)) { test(`${resource} 页面通过编号、名称和溯源号查询跨托盘明细`, async () => { const report = await loadPage(resource) await report.html() for (const [key, value] of [['tunnelCode', 'T-01'], ['tunnelName', '一号隧道'], ['traceNo', 'TR-MIX']]) { assert.ok(report.fields.has(key), `缺少可操作的 ${key} 输入框`) report.fields.get(key)(value) } if (resource === 'return' || resource === 'palletTrace') { assert.ok(report.fields.has('batchNo')) report.fields.get('batchNo')('B-OLD') } report.page.handleQuery() await new Promise(resolve => setImmediate(resolve)) const query = report.requests.at(-1) assert.equal(query.tunnelCode, 'T-01') assert.equal(query.tunnelName, '一号隧道') assert.equal(query.traceNo, 'TR-MIX') assert.equal(query.palletCode, undefined) assert.equal(query.tunnelId, undefined) assert.equal(query.pageNum, 1) }) } test('消耗页展示隧道且无产线历史记录不显示 #null', async () => { const report = await loadPage('consumption', [{ palletCode: 'P-A', tunnelCode: 'T-01', tunnelName: '一号隧道', traceNo: 'TR-MIX', customerId: 1, siteId: 9, strainId: 11, lineId: null, lineName: null, batchNo: 'B-OLD', usedPackages: 30, remainingPackagesAfter: 70 }]) const html = await report.html() assert.match(html, /T-01/) assert.match(html, /一号隧道/) assert.match(html, /TR-MIX/) assert.doesNotMatch(html, /#null|#undefined/) }) test('退回页按历史快照展示溯源关系和批次,不需当前库存', async () => { const report = await loadPage('return', [{ palletCode: 'P-A', tunnelCode: 'T-OLD', tunnelName: '旧隧道', traceNo: 'TR-OLD', batchNo: 'B-OLD', batchNoSnapshot: 'B-OLD', remainingPackagesSnapshot: 70, status: 'CONFIRMED' }]) const html = await report.html() for (const value of ['T-OLD', '旧隧道', 'TR-OLD', 'B-OLD', '70']) assert.ok(html.includes(value), value) }) test('追溯页跨托盘混批逐笔显示,退回申请和确认不会重复计为使用量', async () => { const common = { tunnelCode: 'T-01', tunnelName: '一号隧道', traceNo: 'TR-MIX', beforeTunnelCode: 'T-01', afterTunnelCode: null } const report = await loadPage('palletTrace', [ { ...common, palletCode: 'P-A', batchNoSnapshot: 'B-A', eventType: 'TUNNEL_ENTERED', usedPackages: 0, returnedPackages: 0 }, { ...common, palletCode: 'P-A', batchNoSnapshot: 'B-A', eventType: 'USAGE_RECORDED', usedPackages: 30, returnedPackages: 0 }, { ...common, palletCode: 'P-B', batchNoSnapshot: 'B-B', eventType: 'USAGE_RECORDED', usedPackages: 20, returnedPackages: 0 }, { ...common, palletCode: 'P-A', batchNoSnapshot: 'B-A', eventType: 'RETURN_REQUESTED', usedPackages: 0, returnedPackages: 0 }, { ...common, palletCode: 'P-A', batchNoSnapshot: 'B-A', eventType: 'RETURN_CONFIRMED', usedPackages: 0, returnedPackages: 70 } ]) const html = await report.html() assert.match(html, /入隧登记/) assert.doesNotMatch(html, />TUNNEL_ENTERED { const inventory = await loadPage('palletInventory', [{ palletCode: 'P-A', flowState: 'IN_TRANSIT' }]) const inventoryHtml = await inventory.html() assert.ok(inventoryHtml.includes('待收货'), '库存报表应显示待收货') assert.doesNotMatch(inventoryHtml, />IN_TRANSITSHIPMENT_RECEIVED<|>IN_TRANSIT