fix(FEAT-MO-001): 修复TDX数据完整性与状态展示

This commit is contained in:
2026-08-18 19:30:34 +08:00
parent ff8ada8316
commit 49f6be9c43
20 changed files with 557 additions and 113 deletions
+7 -2
View File
@@ -107,7 +107,7 @@ export interface components {
BreadthDataDto: {
advancing: number;
declining: number;
unchanged?: number | null;
unchanged: number | null;
};
BreadthResponseDto: {
/** Format: date-time */
@@ -128,7 +128,12 @@ export interface components {
FundDataDto: {
changePercent: number;
currentAmount: number;
marketCount: number;
previousAmount: number;
/** @example 20260817 */
previousTradeDate: string;
/** @example 20260818 */
tradeDate: string;
/** @example 亿元 */
unit: string;
};
@@ -184,7 +189,7 @@ export interface components {
items: components['schemas']['SectorQuoteDto'][];
};
SectorQuoteDto: {
changePercent?: number | null;
changePercent: number;
code: string;
/** @enum {string} */
direction: 'up' | 'down' | 'flat';
+16
View File
@@ -297,6 +297,18 @@ textarea {
font-size: 12px;
}
.overview-panel .module-warning {
display: flex;
gap: 8px;
padding: 7px 9px;
border-left: 3px solid #b67a2d;
background: #fff8ec;
}
.overview-panel .module-warning strong {
white-space: nowrap;
}
.overview-grid {
display: grid;
gap: 16px;
@@ -497,6 +509,10 @@ textarea {
color: #19846e;
}
.turnover-trend [data-direction='flat'] {
color: #6f7a84;
}
.turnover-trend {
display: grid;
gap: 12px;
@@ -2,6 +2,10 @@
import type { FundsResponse } from '@/types/market-overview';
defineProps<{ response: FundsResponse | null; loading: boolean }>();
function formatTradeDate(value: string | undefined): string {
return value?.length === 8 ? `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6)}` : '—';
}
</script>
<template>
@@ -11,25 +15,39 @@ defineProps<{ response: FundsResponse | null; loading: boolean }>();
<span class="section-kicker">量能观察</span>
<h2 id="funds-title">成交额趋势</h2>
</div>
<span class="panel-meta">TDX · 亿元</span>
<span class="panel-meta">{{ formatTradeDate(response?.data?.tradeDate) }}</span>
</div>
<div v-if="loading" class="module-placeholder">正在读取资金趋势…</div>
<div v-else-if="response?.status === 'unavailable'" class="module-unavailable">
<strong>暂不可用</strong>
<span>{{ response.warnings[0] }}</span>
</div>
<div v-else-if="response?.data" class="turnover-trend">
<div>
<span>三市成交额</span>
<strong>{{ response.data.currentAmount.toLocaleString('zh-CN') }}</strong>
<small>{{ response.data.unit }}</small>
<template v-else-if="response?.data">
<div v-if="response.status !== 'fresh'" class="module-warning">
<strong>{{ response.status === 'partial' ? '部分数据' : '数据延迟' }}</strong>
<span>{{ response.warnings[0] }}</span>
</div>
<p :data-direction="response.data.changePercent >= 0 ? 'up' : 'down'">
较前一交易日
{{ response.data.changePercent > 0 ? '+' : ''
}}{{ response.data.changePercent.toFixed(2) }}%
</p>
</div>
<div class="turnover-trend">
<div>
<span>{{ response.data.marketCount }} 市成交额</span>
<strong>{{ response.data.currentAmount.toLocaleString('zh-CN') }}</strong>
<small>{{ response.data.unit }}</small>
</div>
<p
:data-direction="
response.data.changePercent === 0
? 'flat'
: response.data.changePercent > 0
? 'up'
: 'down'
"
>
较前一交易日
{{ response.data.changePercent > 0 ? '+' : ''
}}{{ response.data.changePercent.toFixed(2) }}%
</p>
</div>
</template>
<div v-else class="module-placeholder">暂无成交额趋势数据</div>
</section>
</template>
@@ -18,17 +18,23 @@ defineProps<{ response: BreadthResponse | null; loading: boolean }>();
<strong>暂不可用</strong>
<span>{{ response.warnings[0] }}</span>
</div>
<div v-else-if="response?.data" class="breadth-values">
<div>
<span>上涨</span><strong class="positive">{{ response.data.advancing }}</strong>
<template v-else-if="response?.data">
<div v-if="response.status !== 'fresh'" class="module-warning">
<strong>{{ response.status === 'partial' ? '部分数据' : '数据延迟' }}</strong>
<span>{{ response.warnings[0] }}</span>
</div>
<div>
<span>下跌</span><strong class="negative">{{ response.data.declining }}</strong>
<div class="breadth-values">
<div>
<span>上涨</span><strong class="positive">{{ response.data.advancing }}</strong>
</div>
<div>
<span>下跌</span><strong class="negative">{{ response.data.declining }}</strong>
</div>
<div>
<span>平盘</span><strong>{{ response.data.unchanged ?? '—' }}</strong>
</div>
</div>
<div>
<span>平盘</span><strong>{{ response.data.unchanged ?? '—' }}</strong>
</div>
</div>
</template>
<div v-else class="module-placeholder">暂无宽度数据</div>
</section>
</template>
@@ -54,6 +54,52 @@ describe('市场概览真实数据面板', () => {
expect(wrapper.emitted('select')).toEqual([['881319.SH']]);
});
it('市场宽度 partial 状态显示警告且保留有效数据', () => {
const wrapper = mount(MarketBreadth, {
props: {
loading: false,
response: {
...meta,
status: 'partial',
warnings: ['部分市场涨跌家数不可用:北证市场'],
data: { advancing: 10, declining: 5, unchanged: null },
},
},
});
expect(wrapper.text()).toContain('部分数据');
expect(wrapper.text()).toContain('北证市场');
expect(wrapper.text()).toContain('10');
});
it('板块 delayed 状态显示警告和真实交易日期', () => {
const wrapper = mount(SectorOverview, {
props: {
loading: false,
response: {
...meta,
status: 'delayed',
warnings: ['板块数据已延迟'],
data: {
items: [
{
code: '881319.SH',
name: '半导体',
changePercent: 2.56,
direction: 'up',
tradeDate: '20260818',
},
],
},
},
},
});
expect(wrapper.text()).toContain('数据延迟');
expect(wrapper.text()).toContain('板块数据已延迟');
expect(wrapper.text()).toContain('2026-08-18');
});
it('资金模块明确展示成交额和与上一交易日的变化', () => {
const wrapper = mount(FundTrend, {
props: {
@@ -65,6 +111,9 @@ describe('市场概览真实数据面板', () => {
previousAmount: 11000,
changePercent: 12.23,
unit: '亿元' as const,
marketCount: 3,
tradeDate: '20260818',
previousTradeDate: '20260817',
},
},
},
@@ -75,4 +124,32 @@ describe('市场概览真实数据面板', () => {
expect(wrapper.text()).toContain('较前一交易日 +12.23%');
expect(wrapper.text()).not.toContain('主力净流入');
});
it('部分数据保留数值并显示警告和实际交易日期', () => {
const wrapper = mount(FundTrend, {
props: {
loading: false,
response: {
...meta,
status: 'partial',
warnings: ['部分市场成交额不可用,当前汇总 2 个市场'],
data: {
currentAmount: 100,
previousAmount: 100,
changePercent: 0,
unit: '亿元' as const,
marketCount: 2,
tradeDate: '20260818',
previousTradeDate: '20260817',
},
},
},
});
expect(wrapper.text()).toContain('部分数据');
expect(wrapper.text()).toContain('部分市场成交额不可用');
expect(wrapper.text()).toContain('2 市成交额');
expect(wrapper.text()).toContain('2026-08-18');
expect(wrapper.get('[data-direction="flat"]').attributes('data-direction')).toBe('flat');
});
});
@@ -3,6 +3,10 @@ import type { SectorsResponse } from '@/types/market-overview';
defineProps<{ response: SectorsResponse | null; loading: boolean }>();
const emit = defineEmits<{ select: [sector: string] }>();
function formatTradeDate(value: string | undefined): string {
return value?.length === 8 ? `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6)}` : '—';
}
</script>
<template>
@@ -12,32 +16,34 @@ const emit = defineEmits<{ select: [sector: string] }>();
<span class="section-kicker">板块扫描</span>
<h2 id="sectors-title">强弱分布</h2>
</div>
<span class="panel-meta">点击查看详情</span>
<span class="panel-meta">{{ formatTradeDate(response?.data?.items[0]?.tradeDate) }}</span>
</div>
<div v-if="loading" class="module-placeholder">正在读取板块…</div>
<div v-else-if="response?.status === 'unavailable'" class="module-unavailable">
<strong>暂不可用</strong>
<span>{{ response.warnings[0] }}</span>
</div>
<div v-else-if="response?.data?.items.length" class="sector-list">
<button
v-for="sector in response.data.items"
:key="sector.code"
type="button"
:data-direction="sector.direction"
:title="`进入${sector.name}详细分析`"
@click="emit('select', sector.code)"
>
<span>{{ sector.name }}</span>
<strong>
{{
sector.changePercent == null
? '—'
: `${sector.changePercent > 0 ? '+' : ''}${sector.changePercent.toFixed(2)}%`
}}
</strong>
</button>
</div>
<template v-else-if="response?.data?.items.length">
<div v-if="response.status !== 'fresh'" class="module-warning">
<strong>{{ response.status === 'partial' ? '部分数据' : '数据延迟' }}</strong>
<span>{{ response.warnings[0] }}</span>
</div>
<div class="sector-list">
<button
v-for="sector in response.data.items"
:key="sector.code"
type="button"
:data-direction="sector.direction"
:title="`进入${sector.name}详细分析`"
@click="emit('select', sector.code)"
>
<span>{{ sector.name }}</span>
<strong>{{
`${sector.changePercent > 0 ? '+' : ''}${sector.changePercent.toFixed(2)}%`
}}</strong>
</button>
</div>
</template>
<div v-else class="module-placeholder">暂无板块数据</div>
</section>
</template>