feat(FEAT-MO-001): 接入TDX市场宽度板块与成交额趋势
This commit is contained in:
@@ -107,7 +107,7 @@ export interface components {
|
||||
BreadthDataDto: {
|
||||
advancing: number;
|
||||
declining: number;
|
||||
unchanged: number;
|
||||
unchanged?: number | null;
|
||||
};
|
||||
BreadthResponseDto: {
|
||||
/** Format: date-time */
|
||||
@@ -126,7 +126,11 @@ export interface components {
|
||||
status: 'healthy' | 'unavailable' | 'disabled';
|
||||
};
|
||||
FundDataDto: {
|
||||
items: string[];
|
||||
changePercent: number;
|
||||
currentAmount: number;
|
||||
previousAmount: number;
|
||||
/** @example 亿元 */
|
||||
unit: string;
|
||||
};
|
||||
FundsResponseDto: {
|
||||
/** Format: date-time */
|
||||
@@ -177,7 +181,15 @@ export interface components {
|
||||
warnings: string[];
|
||||
};
|
||||
SectorDataDto: {
|
||||
items: string[];
|
||||
items: components['schemas']['SectorQuoteDto'][];
|
||||
};
|
||||
SectorQuoteDto: {
|
||||
changePercent?: number | null;
|
||||
code: string;
|
||||
/** @enum {string} */
|
||||
direction: 'up' | 'down' | 'flat';
|
||||
name: string;
|
||||
tradeDate: string;
|
||||
};
|
||||
SectorsResponseDto: {
|
||||
/** Format: date-time */
|
||||
|
||||
@@ -472,12 +472,54 @@ textarea {
|
||||
}
|
||||
|
||||
.sector-list button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 112px;
|
||||
gap: 10px;
|
||||
padding: 7px 9px;
|
||||
color: #4c5966;
|
||||
background: #f3f7f7;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sector-list button strong {
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.sector-list button[data-direction='up'] strong,
|
||||
.turnover-trend [data-direction='up'] {
|
||||
color: #c33f3f;
|
||||
}
|
||||
|
||||
.sector-list button[data-direction='down'] strong,
|
||||
.turnover-trend [data-direction='down'] {
|
||||
color: #19846e;
|
||||
}
|
||||
|
||||
.turnover-trend {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.turnover-trend div {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.turnover-trend strong {
|
||||
color: #1c2a35;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.turnover-trend small,
|
||||
.turnover-trend p {
|
||||
color: #7d8893;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.overview-grid-top,
|
||||
.overview-grid-bottom {
|
||||
|
||||
@@ -8,16 +8,28 @@ defineProps<{ response: FundsResponse | null; loading: boolean }>();
|
||||
<section class="overview-panel" aria-labelledby="funds-title">
|
||||
<div class="panel-heading">
|
||||
<div>
|
||||
<span class="section-kicker">资金观察</span>
|
||||
<h2 id="funds-title">资金趋势</h2>
|
||||
<span class="section-kicker">量能观察</span>
|
||||
<h2 id="funds-title">成交额趋势</h2>
|
||||
</div>
|
||||
<span class="panel-meta">数据口径</span>
|
||||
<span class="panel-meta">TDX · 亿元</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 class="module-placeholder">暂无资金趋势数据</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>
|
||||
</div>
|
||||
<p :data-direction="response.data.changePercent >= 0 ? 'up' : 'down'">
|
||||
较前一交易日
|
||||
{{ response.data.changePercent > 0 ? '+' : ''
|
||||
}}{{ response.data.changePercent.toFixed(2) }}%
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="module-placeholder">暂无成交额趋势数据</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -26,7 +26,7 @@ defineProps<{ response: BreadthResponse | null; loading: boolean }>();
|
||||
<span>下跌</span><strong class="negative">{{ response.data.declining }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>平盘</span><strong>{{ response.data.unchanged }}</strong>
|
||||
<span>平盘</span><strong>{{ response.data.unchanged ?? '—' }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="module-placeholder">暂无宽度数据</div>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import FundTrend from './FundTrend.vue';
|
||||
import MarketBreadth from './MarketBreadth.vue';
|
||||
import SectorOverview from './SectorOverview.vue';
|
||||
|
||||
const meta = {
|
||||
snapshotId: '20260818-150000',
|
||||
asOf: '2026-08-18T15:00:00.000Z',
|
||||
source: 'tdx' as const,
|
||||
status: 'fresh' as const,
|
||||
warnings: [],
|
||||
};
|
||||
|
||||
describe('市场概览真实数据面板', () => {
|
||||
it('市场宽度对未知平盘家数显示占位符而不是零', () => {
|
||||
const wrapper = mount(MarketBreadth, {
|
||||
props: {
|
||||
loading: false,
|
||||
response: { ...meta, data: { advancing: 2121, declining: 3292, unchanged: null } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toContain('2121');
|
||||
expect(wrapper.text()).toContain('3292');
|
||||
expect(wrapper.text()).toContain('平盘—');
|
||||
});
|
||||
|
||||
it('板块展示名称和涨跌幅并以代码触发详情入口', async () => {
|
||||
const wrapper = mount(SectorOverview, {
|
||||
props: {
|
||||
loading: false,
|
||||
response: {
|
||||
...meta,
|
||||
data: {
|
||||
items: [
|
||||
{
|
||||
code: '881319.SH',
|
||||
name: '半导体',
|
||||
changePercent: 2.56,
|
||||
direction: 'up',
|
||||
tradeDate: '20260818',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toContain('半导体');
|
||||
expect(wrapper.text()).toContain('+2.56%');
|
||||
await wrapper.get('button').trigger('click');
|
||||
expect(wrapper.emitted('select')).toEqual([['881319.SH']]);
|
||||
});
|
||||
|
||||
it('资金模块明确展示成交额和与上一交易日的变化', () => {
|
||||
const wrapper = mount(FundTrend, {
|
||||
props: {
|
||||
loading: false,
|
||||
response: {
|
||||
...meta,
|
||||
data: {
|
||||
currentAmount: 12345.67,
|
||||
previousAmount: 11000,
|
||||
changePercent: 12.23,
|
||||
unit: '亿元' as const,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toContain('成交额趋势');
|
||||
expect(wrapper.text()).toContain('12,345.67');
|
||||
expect(wrapper.text()).toContain('较前一交易日 +12.23%');
|
||||
expect(wrapper.text()).not.toContain('主力净流入');
|
||||
});
|
||||
});
|
||||
@@ -22,11 +22,20 @@ const emit = defineEmits<{ select: [sector: string] }>();
|
||||
<div v-else-if="response?.data?.items.length" class="sector-list">
|
||||
<button
|
||||
v-for="sector in response.data.items"
|
||||
:key="sector"
|
||||
:key="sector.code"
|
||||
type="button"
|
||||
@click="emit('select', sector)"
|
||||
:data-direction="sector.direction"
|
||||
:title="`进入${sector.name}详细分析`"
|
||||
@click="emit('select', sector.code)"
|
||||
>
|
||||
{{ sector }}
|
||||
<span>{{ sector.name }}</span>
|
||||
<strong>
|
||||
{{
|
||||
sector.changePercent == null
|
||||
? '—'
|
||||
: `${sector.changePercent > 0 ? '+' : ''}${sector.changePercent.toFixed(2)}%`
|
||||
}}
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="module-placeholder">暂无板块数据</div>
|
||||
|
||||
Reference in New Issue
Block a user