调整部分前端布局和后端增删改逻辑

This commit is contained in:
焦龙言
2026-09-20 10:38:10 +08:00
parent acddc8a237
commit 3edba99d2d
227 changed files with 122871 additions and 839 deletions
@@ -1076,14 +1076,65 @@ test('重复扫码仅在相同值和时间窗口内成立', async () => {
assert.equal(isDuplicateScan('qr-a', 'qr-b', 1200, 1000, 500), false)
})
test('幂等键由 Web Crypto randomUUID 生成且不降级为伪随机值', async () => {
test('幂等键优先使用 Web Crypto randomUUID', async () => {
const palletScanPath = path.join(frontendRoot, 'src/utils/palletScan.js')
const source = await readFile(palletScanPath, 'utf8')
const { createIdempotencyKey } = await import('../src/utils/palletScan.js')
let getRandomValuesCalled = false
const expectedKey = '12345678-1234-4123-8123-123456789abc'
assert.match(source, /globalThis\.crypto\.randomUUID\(\)/)
const actualKey = createIdempotencyKey({
randomUUID: () => expectedKey,
getRandomValues: () => {
getRandomValuesCalled = true
throw new Error('存在 randomUUID 时不应调用 getRandomValues')
}
})
assert.equal(actualKey, expectedKey)
assert.equal(getRandomValuesCalled, false)
assert.doesNotMatch(source, /Date\.now|Math\.random|new Date/)
assert.match(createIdempotencyKey(), /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
})
test('局域网 HTTP 环境缺少 randomUUID 时使用安全随机数生成 UUID v4', async () => {
const { createIdempotencyKey } = await import('../src/utils/palletScan.js')
const cryptoProvider = {
getRandomValues(bytes) {
bytes.fill(0)
return bytes
}
}
assert.equal(createIdempotencyKey(cryptoProvider), '00000000-0000-4000-8000-000000000000')
})
test('首次入库确认由业务幂等键保护,不被通用短时重复提交规则拦截', async () => {
const source = await readFile(path.join(frontendRoot, 'src/api/business/inbound.js'), 'utf8')
const confirmInboundSource = extractNamedFunction(source, 'confirmInbound')
assert.match(confirmInboundSource, /headers\s*:\s*\{\s*repeatSubmit\s*:\s*false\s*\}/)
})
test('首次入库确认重试前清理旧错误,并明确重试会再次请求服务端', async () => {
const source = await readFile(path.join(frontendRoot, 'src/views/business/inbound/index.vue'), 'utf8')
const confirmSource = extractNamedFunction(source, 'handleConfirm')
const confirmCatch = extractPromiseCallback(confirmSource, 'catch')
const clearErrorIndex = confirmSource.indexOf("loadError.value = ''")
assert.ok(clearErrorIndex >= 0, '入库确认缺少旧错误状态清理')
assert.ok(
clearErrorIndex < confirmSource.indexOf('proxy.$modal.confirm('),
'打开确认框前必须清理上一次的失败状态'
)
assert.match(confirmCatch, /再次确认将重新请求服务端,并复用本次幂等键/)
})
test('首次入库确认失败后取消重试不丢弃原幂等键', async () => {
const source = await readFile(path.join(frontendRoot, 'src/views/business/inbound/index.vue'), 'utf8')
const confirmSource = extractNamedFunction(source, 'handleConfirm')
const confirmCatch = extractPromiseCallback(confirmSource, 'catch')
assert.doesNotMatch(confirmCatch, /clearConfirmIdempotency\(\)|handleConfirmCancellation\(/)
})
test('17 个 API 文件逐一匹配硬编码请求契约', async (t) => {
@@ -0,0 +1,79 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
const dashboardPath = path.resolve(scriptDir, '../src/views/dashboard/index.vue')
const dashboardSource = fs.readFileSync(dashboardPath, 'utf8')
test('首页包含仓储运营总览的核心模块', () => {
for (const label of [
'仓储运营总览',
'当前库存托盘',
'剩余包数',
'今日入库单',
'今日出库单',
'待确认单据',
'新增入库',
'托盘填充',
'发货出库',
'托盘使用',
'托盘退回',
'托盘追踪',
'近 7 天入库 / 出库趋势',
'托盘流转状态分布',
'仓库库存排行',
'最近入库单',
'最近出库单',
'扫码异常',
'待确认业务'
]) {
assert.match(dashboardSource, new RegExp(label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
}
})
test('首页不再包含通用模板示例内容', () => {
for (const sample of [
'项目数',
'团队内排名',
'项目访问',
'Alipay',
'Angular',
'Ant Design',
'XX 指数',
'吴彦祖'
]) {
assert.doesNotMatch(dashboardSource, new RegExp(sample.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
}
})
test('首页使用现有业务报表接口并保留空状态策略', () => {
for (const dependency of [
'@/api/business/inbound',
'@/api/business/shipment',
'@/api/business/return',
'@/api/report/palletInventory',
'@/api/report/shipment'
]) {
assert.match(dashboardSource, new RegExp(dependency.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
}
assert.match(dashboardSource, /暂无扫码异常记录/)
assert.match(dashboardSource, /loadDashboardData/)
})
test('首页快捷入口使用动态菜单注册的页面路径', () => {
for (const route of [
'/businessOperation/inbound',
'/businessOperation/filling',
'/businessOperation/shipment',
'/businessOperation/usage',
'/businessOperation/return',
'/businessReport/palletInventory',
'/businessReport/palletTrace',
'/businessMaster/warehouse'
]) {
assert.match(dashboardSource, new RegExp(route.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')))
}
})