更新登录页及优化相关功能

This commit is contained in:
焦龙言
2026-10-08 14:13:05 +08:00
parent 9a14b692e3
commit 56e646bcc1
139 changed files with 77587 additions and 811 deletions
@@ -0,0 +1,167 @@
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</)
for (const value of ['P-A', 'P-B', 'B-A', 'B-B', 'TR-MIX']) assert.ok(html.includes(value), value)
assert.match(html, /本页实际使用 50 包,确认退回 70 包/)
})
test('报表兼容待收货状态与收货确认事件的中文显示', async () => {
const inventory = await loadPage('palletInventory', [{ palletCode: 'P-A', flowState: 'IN_TRANSIT' }])
const inventoryHtml = await inventory.html()
assert.ok(inventoryHtml.includes('待收货'), '库存报表应显示待收货')
assert.doesNotMatch(inventoryHtml, />IN_TRANSIT</)
const trace = await loadPage('palletTrace', [{
palletCode: 'P-A', eventType: 'SHIPMENT_RECEIVED', beforeState: 'IN_TRANSIT',
afterState: 'AT_SITE_UNUSED', usedPackages: 0, returnedPackages: 0
}])
const traceHtml = await trace.html()
assert.ok(traceHtml.includes('收货确认'), '追溯报表应显示收货确认')
assert.ok(traceHtml.includes('待收货'))
assert.doesNotMatch(traceHtml, />SHIPMENT_RECEIVED<|>IN_TRANSIT</)
assert.match(traceHtml, /本页实际使用 0 包,确认退回 0 包/)
})