优化
This commit is contained in:
@@ -32,6 +32,24 @@ beforeEach(() => {
|
||||
})
|
||||
afterEach(() => { wrappers.splice(0).forEach(wrapper => wrapper.unmount()); vi.useRealTimers(); vi.restoreAllMocks() })
|
||||
describe('warehouse operating dashboard', () => {
|
||||
it.each([
|
||||
['1896.0666666666667', '1,896.07'],
|
||||
[1896.0666666666667, '1,896.07'],
|
||||
['1200', '1,200'],
|
||||
['12.5', '12.5'],
|
||||
['0', '0'],
|
||||
[null, '—'],
|
||||
])('formats the daily average %s without changing the returned metric', async (value, expected) => {
|
||||
const response = structuredClone(dashboardFixture)
|
||||
response.kpis.averageDailyOutboundQuantity.value = value
|
||||
mocks.dashboard.mockResolvedValue(response)
|
||||
const wrapper = page(); await choose(wrapper, { warehouseCode: 'WH-A' })
|
||||
const card = wrapper.findAll('.dashboard-kpis section').find(node => node.find('h3').text() === '所选时段日均出库')!
|
||||
expect(card.find('strong').text()).toBe(expected)
|
||||
expect(card.text()).toContain('件/日')
|
||||
expect(response.kpis.averageDailyOutboundQuantity.value).toBe(value)
|
||||
})
|
||||
|
||||
it.each(['PARTIAL', 'UNKNOWN'])('renders omitted nullable KPI fields for %s instead of rejecting the dashboard', async status => {
|
||||
const response = structuredClone(dashboardFixture)
|
||||
for (const metric of Object.values(response.kpis)) {
|
||||
@@ -246,6 +264,8 @@ describe('warehouse operating dashboard', () => {
|
||||
expect(wrapper.text()).toContain('区间已采集出库量')
|
||||
expect(wrapper.text()).toContain('已知小计 128')
|
||||
expect(wrapper.text()).toContain('另 1 SKU 待核验')
|
||||
expect(wrapper.find('.dashboard-kpis').text()).not.toContain('另 0 SKU 待核验')
|
||||
expect(wrapper.findAll('.dashboard-kpis small').every(node => node.text() !== '—')).toBe(true)
|
||||
expect(wrapper.text()).not.toContain('lingxing-primary')
|
||||
})
|
||||
it('changes the shared date range while pinning versions, then explicitly refreshes the whole snapshot', async () => {
|
||||
|
||||
@@ -70,6 +70,12 @@ const metricNames: { key: keyof Dashboard['kpis']; label: string; inventory?: bo
|
||||
{ key: 'physicalQuantity', label: '实物库存', inventory: true }, { key: 'availableQuantity', label: '正品可售', inventory: true },
|
||||
{ key: 'physicalSkuCount', label: '有库存SKU', inventory: true }, { key: 'noOutboundSkuCount', label: '所选时段无出库SKU', inventory: true },
|
||||
]
|
||||
// 日均值只在展示时舍入,保留原始接口精度和未知状态。
|
||||
function averageDailyText(value: string | number | null | undefined) {
|
||||
if (value == null || String(value).trim() === '') return '—'
|
||||
const number = Number(value)
|
||||
return Number.isFinite(number) ? number.toLocaleString('zh-CN', { maximumFractionDigits: 2 }) : '—'
|
||||
}
|
||||
const metricIcons = [ArrowUpRight, Activity, Boxes, Package, Layers, Clock3]
|
||||
const metricColors = ['#5575ed', '#3aa9ba', '#8871ce', '#35a987', '#c6923e', '#d67b91']
|
||||
const rankingNames = [{ key: 'hotSkus', label: '所选时段热销Top10', unit: '件', all: '查看Top100' }, { key: 'longCoverageSkus', label: '预计消化时间最长Top10', unit: '天', all: '查看全部' }, { key: 'noOutboundSkus', label: '所选时段无出库库存Top10', unit: '件', all: '查看全部' }] as const
|
||||
@@ -212,7 +218,7 @@ onUnmounted(() => { dataFence.cancel(); trendFence.cancel(); poller.stop(); clea
|
||||
<p v-if="includesToday" role="status">所选时段包含今日({{ data.context.anchorDate }}),尚不满足完整自然日要求,库存消化天数与两个库存榜单暂不能完成评估。可选择截至昨日的自定义时段;仍需满足销量覆盖、库存及观察期条件。</p>
|
||||
<p v-if="data.context.rebuildRequired || data.context.salesStatus === 'REBUILD_REQUIRED'" role="status">销量有变动,待重新统计发布。出库趋势展示已发布版本的历史覆盖结果;库存评估等待新版销量发布。</p>
|
||||
<div class="dashboard-kpis" aria-label="本仓经营指标">
|
||||
<section v-for="(metric, index) in metricNames" :key="metric.key" :style="{ '--metric-color': metricColors[index], '--reveal-delay': `${index * 45}ms` }"><div class="dashboard-metric-heading"><h3>{{ metric.key === 'intervalOutboundQuantity' && data.kpis[metric.key].dataStatus !== 'COMPLETE' ? '区间已采集出库量' : metric.label }}</h3><component :is="metricIcons[index]" :size="18" aria-hidden="true" /></div><div class="dashboard-metric-value"><strong>{{ quantityText(metric.key === 'intervalOutboundQuantity' && data.kpis[metric.key].dataStatus !== 'COMPLETE' ? data.kpis[metric.key].observedQuantity : data.kpis[metric.key].value) }}</strong><span> {{ metric.key === 'averageDailyOutboundQuantity' ? '件/日' : data.kpis[metric.key].unit === 'SKU' ? 'SKU' : '件' }}</span></div><p class="dashboard-metric-status">{{ contextExpired || metric.inventory && stockExpired ? '历史/待刷新 · ' : '' }}{{ label(data.kpis[metric.key].dataStatus) }}</p><p v-if="data.kpis[metric.key].value == null && metric.key !== 'intervalOutboundQuantity'">已知小计 {{ quantityText(data.kpis[metric.key].knownSubtotal) }}</p><p v-if="data.kpis[metric.key].unknownSkuCount != null">另 {{ quantityText(data.kpis[metric.key].unknownSkuCount) }} SKU 待核验</p><small>{{ reasonsText(data.kpis[metric.key].reasonCodes) }}</small></section>
|
||||
<section v-for="(metric, index) in metricNames" :key="metric.key" :style="{ '--metric-color': metricColors[index], '--reveal-delay': `${index * 45}ms` }"><div class="dashboard-metric-heading"><h3>{{ metric.key === 'intervalOutboundQuantity' && data.kpis[metric.key].dataStatus !== 'COMPLETE' ? '区间已采集出库量' : metric.label }}</h3><component :is="metricIcons[index]" :size="18" aria-hidden="true" /></div><div class="dashboard-metric-value"><strong>{{ metric.key === 'averageDailyOutboundQuantity' ? averageDailyText(data.kpis[metric.key].value) : quantityText(metric.key === 'intervalOutboundQuantity' && data.kpis[metric.key].dataStatus !== 'COMPLETE' ? data.kpis[metric.key].observedQuantity : data.kpis[metric.key].value) }}</strong><span> {{ metric.key === 'averageDailyOutboundQuantity' ? '件/日' : data.kpis[metric.key].unit === 'SKU' ? 'SKU' : '件' }}</span></div><p class="dashboard-metric-status">{{ contextExpired || metric.inventory && stockExpired ? '历史/待刷新 · ' : '' }}{{ label(data.kpis[metric.key].dataStatus) }}</p><p v-if="data.kpis[metric.key].value == null && metric.key !== 'intervalOutboundQuantity'">已知小计 {{ quantityText(data.kpis[metric.key].knownSubtotal) }}</p><p v-if="Number(data.kpis[metric.key].unknownSkuCount) > 0">另 {{ quantityText(data.kpis[metric.key].unknownSkuCount) }} SKU 待核验</p><small v-if="data.kpis[metric.key].reasonCodes?.length">{{ reasonsText(data.kpis[metric.key].reasonCodes) }}</small></section>
|
||||
</div>
|
||||
<div class="dashboard-main-charts">
|
||||
<section class="dashboard-chart">
|
||||
|
||||
Reference in New Issue
Block a user