This commit is contained in:
2026-10-20 23:49:50 +08:00
parent e60f1b300a
commit 1c771066ea
7 changed files with 121 additions and 4 deletions
@@ -6,7 +6,7 @@
| 页面/组件 | 操作与承载 | 本次调整 | 验收 |
|---|---|---|---|
| AppointmentCalendarView | 调整计划到仓,原520px居中弹框及全屏能力 | 1处日期时间选择器;分钟精度沿用原请求格式;标签宽度150px,说明图标与标签并排,控件沿用公共满宽样式 | 自动检查通过;待人工1920×1080、100%截图确认 |
| AppointmentCalendarView | 调整计划到仓,原520px居中弹框及全屏能力 | 1处日期时间选择器;分钟精度沿用原请求格式;标签宽度150px,说明图标与标签并排,控件沿用公共满宽样式 | 真实浏览器检查通过:日期面板、选值、空值禁用、全屏保值、未保存保护及放弃返回;未提交业务保存 |
| WorkReceivingPanel | 原业务深链中的客户计划、后送与跟进,原表单 | 5处日期/日期时间输入;日期与秒精度的当地时间分别保存;调整开始/结束时段后仍清除原时差依据 | 自动检查通过;待人工截图确认 |
| WorkCommandForm | 跟进、收货要求,原表单 | 3处日期、2处时间输入;跨日结束日期显式保留;不推断次日、不转换地点时区 | 自动检查通过;待人工截图确认 |
| AppointmentBatchForm | 原分批/合并表单 | 2处可空目标日期,保留跨批分配、版本、幂等与原写权限 | 自动检查通过;待人工截图确认 |
@@ -15,6 +15,8 @@
自动验证:受影响7个测试文件88项通过;类型检查与生产构建通过。全量回归执行464个文件4203项,4201项通过,两项旧公共样式断言(标签宽度、追加样式类)更新后,该样式文件65项全部复验通过。后台业务模板原生日期/时间控件残留检查通过。本轮未部署TEST,未进行真实HTTP或人工页面截图验收。
补充浏览器验收:用户明确要求打开页面后,使用已发布TEST页面,页面报告1920×1080、DPR1、visual scale1。TSTU5446539的默认计划、Element日期面板、快捷选值、全屏保值、清空禁用、未保存保护与放弃返回通过;原计划仍2026-10-01,未点击保存。MSCU7264017现已有入库计划,按冻结时间显示且无调整入口。本轮仅覆盖日历与调整到仓弹框,其余三表单仍待逐页操作;输入09:30的最终确认未单独完成,不计通过。证据见 `.logs/appointment-element-release-20261020/ui/result.json` 及同目录截图。
## 2026-10-20 蓝白工作台续批(写权已协调)
本次续批修改文件(相对本模块):
@@ -891,6 +891,41 @@ describe('GlobalSmartLoadingView', () => {
expect(listSmartLoadingCandidates).toHaveBeenCalledWith({ channelGroupCodes: ['MATSON_CLX'], page: 1, size: 20 })
})
it('keeps assignment failures and diagnostic IDs inside the open dialog without losing the quantity', async () => {
vi.spyOn(ElMessage, 'error').mockImplementation(() => undefined as never)
vi.mocked(assignSmartLoadingItem).mockRejectedValueOnce(new AxiosError(
'forbidden', 'ERR_BAD_REQUEST', undefined, undefined,
{ data: { code: 'A0403', message: 'Access denied', requestId: 'assign-request', traceId: 'assign-trace' }, status: 403, statusText: 'Forbidden', headers: {}, config: {} as never },
))
const wrapper = mount(GlobalSmartLoadingView, { attachTo: document.body, global: { plugins: [ElementPlus] } })
await flushPromises()
await wrapper.get('.candidate-pane .el-table__body-wrapper').findAll('button').find(button => button.text() === '加入')!.trigger('click')
await flushPromises()
const popup = wrapper.getComponent(AppDialog)
const input = popup.getComponent(ElInputNumber)
input.vm.$emit('update:modelValue', 7)
await flushPromises()
// 箱数与上方资料使用同一标签列,保留输入名称与原范围。
const label = popup.get('label[for="global-loading-assignment-count"]')
expect(label.element.parentElement?.tagName).toBe('TH')
expect(label.element.parentElement?.nextElementSibling?.getAttribute('colspan')).toBe('3')
expect(popup.find('input#global-loading-assignment-count').exists()).toBe(true)
expect(input.props('max')).toBe(candidate.availablePackageCount)
popup.vm.$emit('confirm')
await flushPromises()
expect(assignSmartLoadingItem).toHaveBeenCalledWith(plan.id, { inboundId: candidate.inboundId, packageCount: 7, expectedVersion: plan.version })
expect(popup.props('modelValue')).toBe(true)
expect(input.props('modelValue')).toBe(7)
expect(popup.text()).toContain('加入当前柜次失败')
expect(popup.text()).toContain('当前账号无权访问该数据')
expect(popup.text()).toContain('assign-request')
expect(popup.text()).toContain('assign-trace')
expect(wrapper.get('.global-loading-page').findAll('.load-error-state')).toHaveLength(1)
wrapper.unmount()
})
it('refreshes the latest plan after A0409 without losing the selected container', async () => {
const warningMessage = vi.spyOn(ElMessage, 'warning').mockImplementation(() => undefined as never)
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
@@ -494,6 +494,7 @@ function openAssign(source: object) {
const candidate = candidateRow(source)
if (candidateBlocked(candidate)) return
selectedCandidate.value = candidate
mutationError.value = null
assignForm.packageCount = candidate.coLoadGroupId
? Number(candidate.groupTotalBoxes || candidate.availablePackageCount)
: candidate.availablePackageCount
@@ -716,7 +717,7 @@ onMounted(() => { void load(); void loadCustomers() })
<template #details><ApiErrorMeta :details="loadError" /></template>
</LoadErrorState>
<template v-else>
<LoadErrorState v-if="mutationError" title="装柜操作失败" :message="mutationError.message" compact retry-text="刷新工作台" @retry="loadWorkbenchData()">
<LoadErrorState v-if="mutationError && !assignOpen" title="装柜操作失败" :message="mutationError.message" compact retry-text="刷新工作台" @retry="loadWorkbenchData()">
<template #details><ApiErrorMeta :details="mutationError" /></template>
</LoadErrorState>
<section class="dispatch-grid" aria-label="智能装柜调度台">
@@ -839,6 +840,9 @@ onMounted(() => { void load(); void loadCustomers() })
</section>
<AppDialog class="admin-density-dialog admin-workbench is-dialog" v-model="assignOpen" :title="assignmentTitle" width="min(920px, calc(100vw - 28px))" fullscreenable :confirm-loading="mutating" :confirm-disabled="Boolean(selectedCandidate?.blockReason || selectedCandidate?.locked)" :confirm-text="selectedCandidate?.unitType === 'SEALED_PALLET' ? '确认整托分配' : selectedCandidate?.coLoadGroupId ? '确认整组分配' : '确认加入'" @confirm="assign">
<LoadErrorState v-if="mutationError" title="加入当前柜次失败" :message="mutationError.message" compact :show-retry="false">
<template #details><ApiErrorMeta :details="mutationError" /></template>
</LoadErrorState>
<div v-if="selectedCandidate" class="density-dialog-sections">
<section class="density-dialog-section" data-tone="source">
<div class="workbench-evidence"><table class="density-table assignment-context-table" aria-label="全局装柜分配来源"><tbody>
@@ -856,7 +860,7 @@ onMounted(() => { void load(); void loadCustomers() })
<template v-else>
<tr><th>入仓码</th><td>{{ selectedCandidate.inboundCode || '—' }}</td><th>可用箱数</th><td>{{ selectedCandidate.availablePackageCount }} 箱</td></tr>
<tr><th>平均单箱毛重</th><td>{{ formatMeasurement(selectedCandidate.unitGrossWeightKg, 'kg', 1) }}</td><th>平均单箱体积</th><td>{{ formatMeasurement(selectedCandidate.averageUnitVolumeCbm, 'm³') }}</td></tr>
<tr><td colspan="4"><el-form-item label="本次分配箱数" label-position="left" label-width="128px" required><el-input-number v-model="assignForm.packageCount" :min="1" :max="selectedCandidate.availablePackageCount" :step="1" controls-position="right" placeholder="请输入本次分配箱数" /></el-form-item></td></tr>
<tr><th><label for="global-loading-assignment-count"><span class="assignment-required" aria-hidden="true">*</span>本次分配箱数</label></th><td colspan="3"><el-form-item><el-input-number id="global-loading-assignment-count" v-model="assignForm.packageCount" aria-label="本次分配箱数" aria-required="true" :min="1" :max="selectedCandidate.availablePackageCount" :step="1" controls-position="right" placeholder="请输入本次分配箱数" /></el-form-item></td></tr>
</template>
</tbody></table></div>
</el-form>
@@ -880,6 +884,7 @@ onMounted(() => { void load(); void loadCustomers() })
<style scoped>
.assignment-context-table { table-layout: fixed; }
.assignment-context-table th { width: 120px; }
.assignment-required { margin-right: var(--admin-space-1); color: var(--el-color-danger); }
.global-loading-page { display: flex; min-width: 0; min-height: 0; flex-direction: column; gap: 12px; }
.embedded-smart-loading { display: flex; width: 100%; min-width: 0; min-height: 0; flex: 1; overflow: hidden; }
.embedded-smart-loading .global-loading-page { width: 100%; min-height: 0; flex: 1; overflow: hidden; }
@@ -2,7 +2,7 @@
## 尾程订单汇总文字隐藏(2026-10-20)
按用户截图`codex-clipboard-168f9aa5-5478-4085-a9b4-d59e7b08ca1f.png`及明确要求,移除查询区下方“仓储转运·全筛选”和“当前筛选中的退货”两段长汇总,不再挂载仅提供转运汇总的组件。订单明细、退货行维度、筛选、分页和错误反馈保持;不删交易数据或改变统计口径。原列表顶部矩阵中的“数量汇总”不再指这两段常驻文字。待人工截图确认。
按用户截图`codex-clipboard-168f9aa5-5478-4085-a9b4-d59e7b08ca1f.png`及明确要求,移除查询区下方“仓储转运·全筛选”和“当前筛选中的退货”两段长汇总,不再挂载仅提供转运汇总的组件。订单明细、退货行维度、筛选、分页和错误反馈保持;不删交易数据或改变统计口径。原列表顶部矩阵中的“数量汇总”不再指这两段常驻文字。本页行为回归75项通过,差异检查通过;待人工截图确认。
## 2026-10-20 列表顶部紧凑收尾
@@ -173,6 +173,43 @@ describe('UnifiedOrdersView list request coordination', () => {
expect(wrapper.findAll('[role="tab"]')[3]!.text()).toBe('测量数据审核(3)')
})
it('keeps every count and the toolbar state stable when switching between ordinary and review tabs', async () => {
vi.mocked(getPlatformUnifiedOrderStatusCounts).mockResolvedValueOnce({ counts: { PENDING_REVIEW: 12, WAIT_MEASURE: 0, MEASUREMENT_REVIEW: 4, SIGNED: 3 } })
const wrapper = mountView()
await flushPromises()
const labels = wrapper.findAll('[role="tab"]').map(tab => tab.text())
vi.mocked(getPlatformUnifiedOrderStatusCounts).mockImplementation(() => new Promise(() => {}))
for (const label of ['待审核(12)', '测量数据审核(4)', '待测量数据(0)', '全部']) {
await wrapper.findAll('[role="tab"]').find(tab => tab.text() === label)!.trigger('click')
await flushPromises()
expect(wrapper.findAll('[role="tab"]').map(tab => tab.text())).toEqual(labels)
expect(wrapper.get('[aria-selected="true"]').text()).toBe(label)
expect(wrapper.get('[role="tablist"]').attributes('aria-busy')).toBe('false')
expect(wrapper.find('.order-status-feedback').exists()).toBe(false)
expect(getPlatformUnifiedOrderStatusCounts).toHaveBeenCalledTimes(1)
}
})
it.each(['success', 'failure'] as const)('ignores an older tab request %s after the current tab has loaded', async (outcome) => {
const wrapper = mountView()
await flushPromises()
let resolveOlder!: (value: Awaited<ReturnType<typeof listPlatformUnifiedOrders>>) => void
let rejectOlder!: (reason: Error) => void
vi.mocked(listPlatformUnifiedOrders).mockImplementationOnce(() => new Promise((resolve, reject) => { resolveOlder = resolve; rejectOlder = reject }))
await wrapper.findAll('[role="tab"]').find(tab => tab.text() === '待审核')!.trigger('click')
const latest = { ...emptyPage(), records: [{ orderId: 'new-order', tenantId: '8', orderVersion: 1, inboundCode: 'IN-NEW', internalOrderStatus: 'SUBMITTED', mainStatus: 'WAIT_MEASURE', expectedArrivalAt: null, estimatedVolumeCbm: null }], total: 1, pages: 1 } as Awaited<ReturnType<typeof listPlatformUnifiedOrders>>
vi.mocked(listPlatformUnifiedOrders).mockResolvedValueOnce(latest)
await wrapper.findAll('[role="tab"]').find(tab => tab.text() === '待测量数据')!.trigger('click')
await flushPromises()
expect(wrapper.getComponent({ name: 'DataTable' }).props('data')).toEqual(latest.records)
if (outcome === 'success') resolveOlder(emptyPage())
else rejectOlder(new Error('旧页签请求失败'))
await flushPromises()
expect(wrapper.get('[aria-selected="true"]').text()).toBe('待测量数据')
expect(wrapper.getComponent({ name: 'DataTable' }).props('data')).toEqual(latest.records)
expect(wrapper.findComponent({ name: 'LoadErrorState' }).exists()).toBe(false)
})
it('loads once initially and once for query, reset, status tab and pagination', async () => {
const wrapper = mountView()
await flushPromises()