Feat: 前端改版 Web 端界面与业务作业流程
按设计稿改版登录后的全部页面,登录页与 Logo 资源未改动。 - 全局外壳:浅色侧边栏与分组菜单、左下角用户卡片、Ctrl/⌘K 搜索面板、 测试模式提示条、深色模式配色、窄屏适配 - 共用组件:页头 PageHeader、托盘流程条 FlowStepper、扫码反馈 ScanFeedback、 缩短单号 DocNo、托盘档案链接 PalletCodeLink、图标 SfIcon、上级资料选择 MasterRefSelect,以及编号段解析等工具 - 工作台重做;新增托盘档案页;托盘追溯增加单托盘入口,报表托盘号可跳转档案 - 基础资料维护:各页加指标条、名称代替 ID、详情面板;三步式批量导入预览; 分配用户对话框标注增减;7 个新增/编辑对话框统一布局 - 业务管理:发货出库、仓库入库、工厂装填、客户现场使用、退回仓库改为 列表 + 详情面板 + 全屏作业区;写入流程、幂等键与防串单逻辑保持不变 - 修复入库页扫码反馈条因同名状态变量遮蔽组件而不显示的问题,并新增契约检查 - 新增入库粘贴解析、退回、装填的行为测试与现场使用页新流程测试, 同步更新业务与工作台契约脚本 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -91,13 +91,15 @@ const expectedRequests = {
|
||||
{ url: "'/master/site'", method: 'post' },
|
||||
{ url: "'/master/site'", method: 'put' },
|
||||
{ url: "'/master/site/' + siteId + '/users'", method: 'get' },
|
||||
{ url: "'/master/site/' + siteId + '/users'", method: 'put' }
|
||||
{ url: "'/master/site/' + siteId + '/users'", method: 'put' },
|
||||
{ url: "'/master/site/customer-options'", method: 'get' }
|
||||
],
|
||||
'src/api/master/line.js': [
|
||||
{ url: "'/master/line/list'", method: 'get' },
|
||||
{ url: "'/master/line/' + lineId", method: 'get' },
|
||||
{ url: "'/master/line'", method: 'post' },
|
||||
{ url: "'/master/line'", method: 'put' },
|
||||
{ url: "'/master/line/site-options'", method: 'get' }
|
||||
],
|
||||
'src/api/master/warehouse.js': [
|
||||
{ url: "'/master/warehouse/list'", method: 'get' },
|
||||
@@ -119,9 +121,11 @@ const expectedRequests = {
|
||||
{ url: "'/master/tunnel/' + tunnelId", method: 'get' },
|
||||
{ url: "'/master/tunnel'", method: 'post' },
|
||||
{ url: "'/master/tunnel'", method: 'put' },
|
||||
{ url: "'/master/tunnel/site-options'", method: 'get' }
|
||||
],
|
||||
'src/api/business/inbound.js': [
|
||||
{ url: "'/business/inbound/list'", method: 'get' },
|
||||
{ url: "'/business/inbound/summary'", method: 'get' },
|
||||
{ url: "'/business/inbound/warehouse-options'", method: 'get' },
|
||||
{ url: "'/business/inbound/scan/' + encodeURIComponent(qrToken)", method: 'get' },
|
||||
{ url: "'/business/inbound/' + inboundId", method: 'get' },
|
||||
@@ -131,6 +135,9 @@ const expectedRequests = {
|
||||
],
|
||||
'src/api/business/filling.js': [
|
||||
{ url: "'/business/filling/list'", method: 'get' },
|
||||
{ url: "'/business/filling/summary'", method: 'get' },
|
||||
{ url: "'/business/filling/batches'", method: 'get' },
|
||||
{ url: "'/business/filling/batches/detail'", method: 'get' },
|
||||
{ url: "'/business/filling/strain-options'", method: 'get' },
|
||||
{ url: "'/business/filling/scan/' + encodeURIComponent(qrToken)", method: 'get' },
|
||||
{ url: "'/business/filling/' + fillingId", method: 'get' },
|
||||
@@ -151,6 +158,9 @@ const expectedRequests = {
|
||||
{ url: "'/business/usage/list'", method: 'get' },
|
||||
{ url: "'/business/usage/line-options'", method: 'get' },
|
||||
{ url: "'/business/usage/tunnel-options'", method: 'get' },
|
||||
{ url: "'/business/usage/site-options'", method: 'get' },
|
||||
{ url: "'/business/usage/site-board'", method: 'get' },
|
||||
{ url: "'/business/usage-fifo/queue'", method: 'get' },
|
||||
{ url: "'/business/usage/enter-tunnel'", method: 'post' },
|
||||
{ url: "'/business/usage/scan/' + encodeURIComponent(qrToken)", method: 'get' },
|
||||
{ url: "'/business/usage-fifo/recommendation'", method: 'get' },
|
||||
@@ -159,6 +169,8 @@ const expectedRequests = {
|
||||
],
|
||||
'src/api/business/return.js': [
|
||||
{ url: "'/business/return/list'", method: 'get' },
|
||||
{ url: "'/business/return/summary'", method: 'get' },
|
||||
{ url: "'/business/return/site-options'", method: 'get' },
|
||||
{ url: "'/business/return/warehouse-options'", method: 'get' },
|
||||
{ url: "'/business/return/scan/' + encodeURIComponent(qrToken)", method: 'get' },
|
||||
{ url: "'/business/return/request'", method: 'post' },
|
||||
@@ -211,14 +223,14 @@ const masterPageContracts = {
|
||||
},
|
||||
site: {
|
||||
relativePath: 'src/views/master/site/index.vue',
|
||||
apiNames: ['addSite', 'getSite', 'listSite', 'updateSite'],
|
||||
apiNames: ['addSite', 'getSite', 'getSiteCustomerOptions', 'listSite', 'updateSite'],
|
||||
permissions: ['add', 'assign', 'edit', 'list', 'query'],
|
||||
addPayloadKeys: ['address', 'customerId', 'remark', 'siteCode', 'siteName', 'status'],
|
||||
editPayloadKeys: ['address', 'customerId', 'remark', 'siteCode', 'siteId', 'siteName', 'status']
|
||||
},
|
||||
line: {
|
||||
relativePath: 'src/views/master/line/index.vue',
|
||||
apiNames: ['addLine', 'getLine', 'listLine', 'updateLine'],
|
||||
apiNames: ['addLine', 'getLine', 'getLineSiteOptions', 'listLine', 'updateLine'],
|
||||
permissions: ['add', 'edit', 'list', 'query'],
|
||||
addPayloadKeys: ['lineCode', 'lineName', 'remark', 'siteId', 'status'],
|
||||
editPayloadKeys: ['lineCode', 'lineId', 'lineName', 'remark', 'siteId', 'status']
|
||||
@@ -246,6 +258,7 @@ const businessPageContracts = {
|
||||
'addInbound',
|
||||
'confirmInbound',
|
||||
'getInbound',
|
||||
'getInboundSummary',
|
||||
'getInboundWarehouseOptions',
|
||||
'listInbound',
|
||||
'scanInboundPallet',
|
||||
@@ -260,7 +273,15 @@ const businessPageContracts = {
|
||||
},
|
||||
filling: {
|
||||
relativePath: 'src/views/business/filling/index.vue',
|
||||
apiNames: ['addFilling', 'getFillingStrainOptions', 'listFilling', 'scanFillingPallet'],
|
||||
apiNames: [
|
||||
'addFilling',
|
||||
'getFillingBatchDetail',
|
||||
'getFillingStrainOptions',
|
||||
'getFillingSummary',
|
||||
'listFilling',
|
||||
'listFillingBatches',
|
||||
'scanFillingPallet'
|
||||
],
|
||||
permissions: ['add', 'list', 'query'],
|
||||
payloads: {
|
||||
addFilling: [
|
||||
@@ -298,7 +319,18 @@ const businessPageContracts = {
|
||||
},
|
||||
usage: {
|
||||
relativePath: 'src/views/business/usage/index.vue',
|
||||
apiNames: ['addUsage', 'enterUsageTunnel', 'getUsageFifoRecommendation', 'getUsageLineOptions', 'getUsageTunnelOptions', 'listUsage', 'scanUsagePallet'],
|
||||
apiNames: [
|
||||
'addUsage',
|
||||
'enterUsageTunnel',
|
||||
'getUsageFifoQueue',
|
||||
'getUsageFifoRecommendation',
|
||||
'getUsageLineOptions',
|
||||
'getUsageSiteBoard',
|
||||
'getUsageSiteOptions',
|
||||
'getUsageTunnelOptions',
|
||||
'listUsage',
|
||||
'scanUsagePallet'
|
||||
],
|
||||
permissions: ['add', 'list', 'query'],
|
||||
// 使用请求需原样持久化以安全重试,载荷边界由 usage-behavior 的真实请求断言验证。
|
||||
payloads: {}
|
||||
@@ -308,6 +340,8 @@ const businessPageContracts = {
|
||||
apiNames: [
|
||||
'confirmReturn',
|
||||
'getReturn',
|
||||
'getReturnSiteOptions',
|
||||
'getReturnSummary',
|
||||
'getReturnWarehouseOptions',
|
||||
'listReturn',
|
||||
'requestReturn',
|
||||
@@ -1538,7 +1572,7 @@ test('首次入库确认失败后取消重试不丢弃原幂等键', async () =>
|
||||
|
||||
test('18 个 API 文件逐一匹配硬编码请求契约', async (t) => {
|
||||
assert.equal(Object.keys(expectedRequests).length, 18)
|
||||
assert.equal(Object.values(expectedRequests).flat().length, 74)
|
||||
assert.equal(Object.values(expectedRequests).flat().length, 86)
|
||||
|
||||
for (const [relativePath, expected] of Object.entries(expectedRequests)) {
|
||||
await t.test(relativePath, async () => {
|
||||
@@ -2923,3 +2957,20 @@ test('三个业务菜单公共样式统一使用 Element Plus 主题变量', asy
|
||||
assert.doesNotMatch(styles, /background:\s*#f7f9fc\s*!important/)
|
||||
assert.doesNotMatch(styles, /background:\s*#fbfcfe\s*;/)
|
||||
})
|
||||
|
||||
test('业务页面导入的组件不被同名响应式状态遮蔽', async (t) => {
|
||||
// 非 .vue 路径导入的组件按 maybe-ref 绑定,模板标签会优先解析为同名 camelCase 状态(如 scanFeedback 遮蔽 ScanFeedback)
|
||||
const { compileScript, parse } = await import('@vue/compiler-sfc')
|
||||
for (const [resource, contract] of Object.entries(businessPageContracts)) {
|
||||
await t.test(resource, async () => {
|
||||
const source = await readFile(path.join(frontendRoot, contract.relativePath), 'utf8')
|
||||
const { descriptor } = parse(source)
|
||||
const compiled = compileScript(descriptor, { id: `contract-${resource}`, inlineTemplate: true }).content
|
||||
const components = [...source.matchAll(/^import\s+([A-Z]\w*)\s+from\s+['"]@\/components\/[^'"]+['"]/gm)].map((match) => match[1])
|
||||
for (const component of components) {
|
||||
const shadow = component[0].toLowerCase() + component.slice(1)
|
||||
assert.doesNotMatch(compiled, new RegExp(`_create(?:VNode|Block)\\(_unref\\(${shadow}\\)`), `${component} 被同名状态 ${shadow} 遮蔽`)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user