Release/test 合并上生产 #2
@@ -35,6 +35,8 @@ describe('InboundTaskList measurement scope', () => {
|
||||
|
||||
expect(source).toContain(':tenant-id="selectedInbound.tenantId"')
|
||||
expect(source).toContain(':customer-id="selectedInbound.tenantId"')
|
||||
expect(source).toContain(':customer-name="selectedInbound.customerName || selectedInbound.companyName"')
|
||||
expect(source).toContain(':channel-name="selectedInbound.channelName"')
|
||||
expect(source).not.toContain('selectedInbound.customerId || selectedInbound.tenantId')
|
||||
})
|
||||
|
||||
|
||||
@@ -361,6 +361,9 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
|
||||
v-if="selectedInbound"
|
||||
:tenant-id="selectedInbound.tenantId"
|
||||
:inbound-id="selectedInbound.id"
|
||||
:customer-name="selectedInbound.customerName || selectedInbound.companyName"
|
||||
:channel-name="selectedInbound.channelName"
|
||||
:channel-code="selectedInbound.channelCode"
|
||||
@changed="handleTaskChanged"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
@@ -38,6 +38,8 @@ describe('InboundWorkflowWorkspace', () => {
|
||||
expect(source).toContain('@click="measurementDialogOpen = true"')
|
||||
expect(source).toContain('@click="reviewDialogOpen = true"')
|
||||
expect(source).toContain(':key="`review-dialog-${revision}`"')
|
||||
expect(source).toContain(':customer-name="detail?.summary.customerName || detail?.summary.companyName"')
|
||||
expect(source).toContain(':channel-name="detail?.summary.channelName"')
|
||||
expect(source).toContain('@changed="handleReviewChanged"')
|
||||
expect(source).not.toContain('<template #footer>')
|
||||
expect(source).toContain('@updated="handleDomainChanged"')
|
||||
|
||||
@@ -210,6 +210,9 @@ onBeforeUnmount(() => {
|
||||
:key="`review-dialog-${revision}`"
|
||||
:tenant-id="customerId"
|
||||
:inbound-id="inboundId"
|
||||
:customer-name="detail?.summary.customerName || detail?.summary.companyName"
|
||||
:channel-name="detail?.summary.channelName"
|
||||
:channel-code="detail?.summary.channelCode"
|
||||
@changed="handleReviewChanged"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('MeasurementReviewPanel', () => {
|
||||
vi.mocked(listMeasurementReviewHistory).mockResolvedValue({ records: [], page: 1, size: 20, total: 0, pages: 0 })
|
||||
})
|
||||
|
||||
it('keeps the pending decision and refresh actions in a sticky first-screen toolbar', async () => {
|
||||
it('keeps customer context and pending actions in one non-sticky first-screen toolbar', async () => {
|
||||
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
|
||||
@@ -65,14 +65,14 @@ describe('MeasurementReviewPanel', () => {
|
||||
expect(toolbar.text()).toContain('客户:柏悦客户')
|
||||
expect(toolbar.text()).toContain('渠道:海运')
|
||||
expect(wrapper.find('.review-toolbar__context').exists()).toBe(true)
|
||||
expect(wrapper.get('.review-toolbar__status').find('.review-toolbar__context').exists()).toBe(true)
|
||||
expect(wrapper.find('.review-context').exists()).toBe(false)
|
||||
expect(wrapper.text()).not.toContain('RK90001')
|
||||
expect(wrapper.text()).not.toContain('入仓码')
|
||||
expect(wrapper.text()).not.toContain('驳回')
|
||||
expect(wrapper.text()).not.toContain('异常放行')
|
||||
const source = await import('./MeasurementReviewPanel.vue?raw').then((module) => module.default)
|
||||
expect(source).toContain('position: sticky')
|
||||
expect(source).toContain('review-toolbar__main')
|
||||
expect(source).not.toContain('position: sticky')
|
||||
expect(source).toContain('review-toolbar__context')
|
||||
expect(source.indexOf('class="review-toolbar"')).toBeLessThan(source.indexOf('<WeightOcrPanel'))
|
||||
expect(source).not.toContain('reasonDialog')
|
||||
@@ -80,6 +80,17 @@ describe('MeasurementReviewPanel', () => {
|
||||
expect(source).not.toContain('submitInboundMeasurement')
|
||||
})
|
||||
|
||||
it('uses the list customer context when the review detail has no customer name', async () => {
|
||||
vi.mocked(getMeasurementReviewDetail).mockResolvedValue({ ...detail, companyName: null })
|
||||
const wrapper = mount(MeasurementReviewPanel, {
|
||||
props: { customerId: '10001', inboundId: '90001', customerName: '欧阳的公司', channelName: '纽约海铁' },
|
||||
global: { plugins: [ElementPlus] },
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.get('.review-toolbar__context').text()).toBe('客户:欧阳的公司 · 渠道:海运')
|
||||
})
|
||||
|
||||
it('reuses WeightOcrPanel and enables only its review correction mode', async () => {
|
||||
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
|
||||
@@ -16,6 +16,9 @@ const props = defineProps<{
|
||||
/** @deprecated 仅用于兼容尚未完成 customerId 映射的上层集成。 */
|
||||
tenantId?: string
|
||||
inboundId: string
|
||||
customerName?: string | null
|
||||
channelName?: string | null
|
||||
channelCode?: string | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ changed: [] }>()
|
||||
@@ -36,6 +39,8 @@ const customerId = computed(() => props.customerId || props.tenantId || '')
|
||||
const isPending = computed(() => detail.value?.measureReviewStatus === 'PENDING' && detail.value?.processStatus === 'MEASURE_PENDING')
|
||||
const isRejected = computed(() => detail.value?.measureReviewStatus === 'REJECTED' && detail.value?.processStatus === 'MEASURE_REJECTED')
|
||||
const reviewStatusLabel = computed(() => inboundStatusLabel(detail.value?.measureReviewStatus, inboundReviewStatusLabels, '未提交'))
|
||||
const displayCustomerName = computed(() => detail.value?.companyName?.trim() || props.customerName?.trim() || '客户未命名')
|
||||
const displayChannelName = computed(() => detail.value?.channelName?.trim() || detail.value?.channelCode?.trim() || props.channelName?.trim() || props.channelCode?.trim() || '渠道未填写')
|
||||
|
||||
function formatDate(value: string | null | undefined) {
|
||||
return value ? value.replace('T', ' ') : '—'
|
||||
@@ -135,20 +140,18 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
|
||||
<section class="review-panel" aria-labelledby="measurement-review-title">
|
||||
<h2 id="measurement-review-title" class="sr-only">测量审核内容</h2>
|
||||
<div class="review-toolbar" aria-label="测量审核操作">
|
||||
<div class="review-toolbar__main">
|
||||
<div class="review-toolbar__status">
|
||||
<StatusBadge v-if="detail" :status="reviewStatusLabel" />
|
||||
<span v-if="isPending">通过后进入待分柜</span>
|
||||
<span v-else-if="isRejected">历史记录为审核驳回,仅供查看</span>
|
||||
<span v-else>当前记录不可审核</span>
|
||||
</div>
|
||||
<div class="review-toolbar__actions">
|
||||
<el-button v-if="isPending && canReview" type="primary" plain @click="correctionMode = true"><Pencil :size="16" />修改重量</el-button>
|
||||
<el-button v-if="isPending && canReview" type="primary" :loading="actionLoading === 'approve'" @click="confirmApprove"><Check :size="16" />通过并进入待分柜</el-button>
|
||||
<el-button :loading="loading || historyLoading" aria-label="刷新审核数据" @click="reload"><RotateCcw :size="16" />刷新</el-button>
|
||||
</div>
|
||||
<div class="review-toolbar__status">
|
||||
<StatusBadge v-if="detail" :status="reviewStatusLabel" />
|
||||
<span v-if="isPending">通过后进入待分柜</span>
|
||||
<span v-else-if="isRejected">历史记录为审核驳回,仅供查看</span>
|
||||
<span v-else>当前记录不可审核</span>
|
||||
<span v-if="detail" class="review-toolbar__context">客户:{{ displayCustomerName }} · 渠道:{{ displayChannelName }}</span>
|
||||
</div>
|
||||
<div class="review-toolbar__actions">
|
||||
<el-button v-if="isPending && canReview" type="primary" plain @click="correctionMode = true"><Pencil :size="16" />修改重量</el-button>
|
||||
<el-button v-if="isPending && canReview" type="primary" :loading="actionLoading === 'approve'" @click="confirmApprove"><Check :size="16" />通过并进入待分柜</el-button>
|
||||
<el-button :loading="loading || historyLoading" aria-label="刷新审核数据" @click="reload"><RotateCcw :size="16" />刷新</el-button>
|
||||
</div>
|
||||
<p v-if="detail" class="review-toolbar__context">客户:{{ detail.companyName || '客户未命名' }} · 渠道:{{ detail.channelName || detail.channelCode || '渠道未填写' }}</p>
|
||||
</div>
|
||||
|
||||
<el-alert v-if="loadError" type="error" :closable="false" show-icon role="alert">
|
||||
@@ -187,13 +190,12 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
|
||||
|
||||
<style scoped>
|
||||
.review-panel { display: grid; gap: 16px; color: var(--cc-text); }
|
||||
.review-toolbar { position: sticky; z-index: 4; top: 0; display: grid; gap: 10px; margin: -20px -24px 0; padding: 14px 24px 12px; border-bottom: 1px solid var(--cc-border); background: #fff; box-shadow: 0 4px 12px rgb(16 24 40 / 6%); }
|
||||
.review-toolbar__main { display: flex; min-width: 0; align-items: center; justify-content: space-between; gap: 14px; }
|
||||
.review-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin: -20px -24px 0; padding: 14px 24px; border-bottom: 1px solid var(--cc-border); background: #fff; }
|
||||
.review-toolbar__status, .review-toolbar__actions { display: flex; min-width: 0; align-items: center; flex-wrap: wrap; gap: 8px; }
|
||||
.review-toolbar__status > span { color: var(--cc-muted); font-size: 12px; }
|
||||
.review-toolbar__actions { justify-content: flex-end; }
|
||||
.review-toolbar__actions :deep(.el-button) { margin-left: 0; }
|
||||
.review-toolbar__context { margin: 0; padding-top: 10px; border-top: 1px solid var(--cc-border); color: var(--cc-muted); font-size: 13px; line-height: 1.55; overflow-wrap: anywhere; }
|
||||
.review-toolbar__status > .review-toolbar__context { padding-left: 8px; border-left: 1px solid var(--cc-border); font-size: 13px; line-height: 1.55; overflow-wrap: anywhere; }
|
||||
.review-alert-title { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.review-history { border-top: 1px solid var(--cc-border); border-bottom: 0; }
|
||||
.review-history :deep(.el-collapse-item__header) { height: 42px; color: var(--cc-muted); font-size: 12px; }
|
||||
@@ -209,8 +211,8 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
|
||||
.history-content time, .history-content > span { color: var(--cc-muted); font-size: 11px; }
|
||||
.history-content p { margin: 5px 0 4px; color: var(--cc-text); font-size: 13px; overflow-wrap: anywhere; }
|
||||
@media (max-width: 720px) {
|
||||
.review-toolbar { margin: -14px -14px 0; padding: 12px 14px; }
|
||||
.review-toolbar__main { align-items: stretch; flex-direction: column; }
|
||||
.review-toolbar { align-items: stretch; flex-direction: column; margin: -14px -14px 0; padding: 12px 14px; }
|
||||
.review-toolbar__status > .review-toolbar__context { width: 100%; padding-top: 8px; padding-left: 0; border-top: 1px solid var(--cc-border); border-left: 0; }
|
||||
.review-toolbar__actions { display: grid; width: 100%; grid-template-columns: 1fr; justify-content: stretch; }
|
||||
.review-toolbar__actions :deep(.el-button) { width: 100%; min-height: 44px; }
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ describe('WeightOcrPanel contract', () => {
|
||||
expect(source).toContain('<ApiErrorMeta')
|
||||
expect(source).toContain('<LoadErrorState')
|
||||
expect(source).toContain('尚未上传称重图片')
|
||||
expect(source).not.toContain('称重数据仅供查看')
|
||||
expect(source).toContain('v-loading="loading"')
|
||||
expect(source).toContain('@media (max-width: 720px)')
|
||||
expect(source).toContain('@media (max-width: 360px)')
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
} from '../api/weightOcr'
|
||||
import type { WeightPalletOption, WeightResultResource, WeightWorkspaceResource } from '../types'
|
||||
import WeightImageThumbnail from './WeightImageThumbnail.vue'
|
||||
import { inboundProcessStatusLabels, inboundStatusLabel } from '../../status'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
customerId?: string
|
||||
@@ -499,10 +498,6 @@ defineExpose({ reload: loadWorkspace })
|
||||
>
|
||||
<el-button size="small" @click="loadPalletOptions"><RefreshCw :size="14" />重新加载</el-button>
|
||||
</el-alert>
|
||||
<el-alert v-if="workspace && !editable && !reviewCorrectionEnabled" type="info" :closable="false" show-icon class="weight-state-note">
|
||||
当前入库状态为 {{ inboundStatusLabel(workspace.processStatus, inboundProcessStatusLabels, '未知状态') }},称重数据仅供查看。
|
||||
</el-alert>
|
||||
|
||||
<div v-loading="loading" class="weight-list" aria-live="polite">
|
||||
<div v-if="!loading && rows.length === 0" class="weight-empty">
|
||||
<strong>尚未上传称重图片</strong>
|
||||
@@ -625,7 +620,7 @@ defineExpose({ reload: loadWorkspace })
|
||||
.weight-summary span { color: var(--cc-muted); font-size: 12px; }
|
||||
.weight-summary strong { color: #067647; font-size: 18px; }
|
||||
.visually-hidden-input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
|
||||
.weight-state-note, .weight-action-error { margin-bottom: 14px; }
|
||||
.weight-action-error { margin-bottom: 14px; }
|
||||
.weight-list { min-height: 0; margin-top: 14px; }
|
||||
.weight-empty { display: flex; min-height: 0; align-items: center; gap: 10px; padding: 4px 0 0; color: var(--cc-muted); }
|
||||
.weight-empty strong { color: #344054; font-size: 14px; }
|
||||
|
||||
Reference in New Issue
Block a user