diff --git a/web/src/api/market-overview/index.test.ts b/web/src/api/market-overview/index.test.ts index f06bba6..78093e2 100644 --- a/web/src/api/market-overview/index.test.ts +++ b/web/src/api/market-overview/index.test.ts @@ -17,6 +17,9 @@ describe('市场概览业务 API', () => { await api.getBreadth(); await api.getSectors(); await api.getFunds(); + await api.getSectorRankings({ category: 'industry', sort: 'rise', limit: 10 }); + await api.getCapitalFlow({ category: 'industry', sort: 'inflow', limit: 10 }); + await api.getTurnoverTrend({ interval: '5' }); await api.getShortTermEmotion(); await api.getShortTermConclusion(); @@ -26,6 +29,9 @@ describe('市场概览业务 API', () => { '/api/v1/market-overview/breadth', '/api/v1/market-overview/sectors', '/api/v1/market-overview/funds', + '/api/v1/market-overview/sector-rankings', + '/api/v1/market-overview/capital-flow', + '/api/v1/market-overview/turnover-trend', '/api/v1/market-overview/short-term/emotion', '/api/v1/market-overview/short-term/conclusion', ]); diff --git a/web/src/api/market-overview/index.ts b/web/src/api/market-overview/index.ts index b214a99..349e22a 100644 --- a/web/src/api/market-overview/index.ts +++ b/web/src/api/market-overview/index.ts @@ -8,9 +8,11 @@ import type { ShortTermEmotionResponse, SummaryResponse, } from '@/types/market-overview'; +import { createMarketStructureApi } from './structure'; export function createMarketOverviewApi(requestClient: RequestClient = request) { return { + ...createMarketStructureApi(requestClient), getSummary: () => requestClient({ method: 'get', url: '/api/v1/market-overview/summary' }), getIndices: () => diff --git a/web/src/api/market-overview/structure.test.ts b/web/src/api/market-overview/structure.test.ts new file mode 100644 index 0000000..e4189e1 --- /dev/null +++ b/web/src/api/market-overview/structure.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it, vi } from 'vitest'; + +import type { RequestClient } from '@/utils/request'; +import { createMarketStructureApi } from './structure'; + +describe('市场结构业务 API', () => { + it('使用独立版本化路径和白名单查询参数', async () => { + const requestMock = vi.fn((config: Parameters[0]) => { + void config; + return Promise.resolve({}); + }); + const api = createMarketStructureApi(requestMock as unknown as RequestClient); + + await api.getSectorRankings({ category: 'industry', sort: 'rise', limit: 10 }); + await api.getCapitalFlow({ category: 'concept', sort: 'outflow', limit: 20 }); + await api.getTurnoverTrend({ interval: '5', tradeDate: '20260825' }); + + expect(requestMock.mock.calls).toEqual([ + [ + expect.objectContaining({ + method: 'get', + url: '/api/v1/market-overview/sector-rankings', + params: { category: 'industry', sort: 'rise', limit: 10 }, + }), + ], + [ + expect.objectContaining({ + method: 'get', + url: '/api/v1/market-overview/capital-flow', + params: { category: 'concept', sort: 'outflow', limit: 20 }, + }), + ], + [ + expect.objectContaining({ + method: 'get', + url: '/api/v1/market-overview/turnover-trend', + params: { interval: '5', tradeDate: '20260825' }, + }), + ], + ]); + }); +}); diff --git a/web/src/api/market-overview/structure.ts b/web/src/api/market-overview/structure.ts new file mode 100644 index 0000000..60c63af --- /dev/null +++ b/web/src/api/market-overview/structure.ts @@ -0,0 +1,51 @@ +import request, { type RequestClient } from '@/utils/request'; +import type { components } from '@/api/generated/schema'; + +export type SectorRankingsResponse = components['schemas']['SectorRankingsResponseDto']; +export type CapitalFlowResponse = components['schemas']['CapitalFlowResponseDto']; +export type TurnoverTrendResponse = components['schemas']['TurnoverTrendResponseDto']; + +export interface SectorRankingsQuery { + category: 'industry' | 'concept' | 'board'; + sort: 'rise' | 'fall' | 'turnover'; + limit: number; +} + +export interface CapitalFlowQuery { + category: 'industry' | 'concept' | 'board'; + sort: 'inflow' | 'outflow'; + limit: number; +} + +export interface TurnoverTrendQuery { + interval: '5' | '15'; + tradeDate?: string; +} + +/** 市场结构请求只调用固定的 Nest 路径,页面不感知 AKTools/TDX 地址。 */ +export function createMarketStructureApi(requestClient: RequestClient = request) { + return { + getSectorRankings: (params: SectorRankingsQuery) => + requestClient({ + method: 'get', + url: '/api/v1/market-overview/sector-rankings', + params, + }), + getCapitalFlow: (params: CapitalFlowQuery) => + requestClient({ + method: 'get', + url: '/api/v1/market-overview/capital-flow', + params, + }), + getTurnoverTrend: (params: TurnoverTrendQuery) => + requestClient({ + method: 'get', + url: '/api/v1/market-overview/turnover-trend', + params, + }), + }; +} + +const marketStructureApi = createMarketStructureApi(); + +export default marketStructureApi; diff --git a/web/src/types/market-overview.ts b/web/src/types/market-overview.ts index 65ff52e..8c29749 100644 --- a/web/src/types/market-overview.ts +++ b/web/src/types/market-overview.ts @@ -7,6 +7,9 @@ export type SectorsResponse = components['schemas']['SectorsResponseDto']; export type FundsResponse = components['schemas']['FundsResponseDto']; export type ShortTermEmotionResponse = components['schemas']['ShortTermEmotionResponseDto']; export type ShortTermConclusionResponse = components['schemas']['ShortTermConclusionResponseDto']; +export type SectorRankingsResponse = components['schemas']['SectorRankingsResponseDto']; +export type CapitalFlowResponse = components['schemas']['CapitalFlowResponseDto']; +export type TurnoverTrendResponse = components['schemas']['TurnoverTrendResponseDto']; export type SummaryData = NonNullable; export type IndicesData = NonNullable; export type BreadthData = NonNullable; @@ -14,5 +17,8 @@ export type SectorsData = NonNullable; export type FundsData = NonNullable; export type ShortTermEmotionData = NonNullable; export type ShortTermConclusionData = NonNullable; +export type SectorRankingsData = NonNullable; +export type CapitalFlowData = NonNullable; +export type TurnoverTrendData = NonNullable; export type OverviewModuleStatus = 'idle' | 'loading' | 'ready' | 'error'; diff --git a/web/src/views/market-overview/useMarketOverview.test.ts b/web/src/views/market-overview/useMarketOverview.test.ts index ce0e654..ad43a71 100644 --- a/web/src/views/market-overview/useMarketOverview.test.ts +++ b/web/src/views/market-overview/useMarketOverview.test.ts @@ -19,6 +19,9 @@ describe('市场概览状态', () => { getBreadth: vi.fn().mockResolvedValue(response), getSectors: vi.fn().mockResolvedValue(response), getFunds: vi.fn().mockResolvedValue(response), + getSectorRankings: vi.fn().mockResolvedValue(response), + getCapitalFlow: vi.fn().mockResolvedValue(response), + getTurnoverTrend: vi.fn().mockResolvedValue(response), getShortTermEmotion: vi.fn().mockResolvedValue(response), getShortTermConclusion: vi.fn().mockResolvedValue(response), }; @@ -32,6 +35,9 @@ describe('市场概览状态', () => { expect(state.isLoading.value).toBe(false); expect(state.shortTermEmotion.value.status).toBe('ready'); expect(state.shortTermConclusion.value.status).toBe('ready'); + expect(state.sectorRankings.value.status).toBe('ready'); + expect(state.capitalFlow.value.status).toBe('ready'); + expect(state.turnoverTrend.value.status).toBe('ready'); }); it('分析偏好写入本地存储并可恢复', () => { diff --git a/web/src/views/market-overview/useMarketOverview.ts b/web/src/views/market-overview/useMarketOverview.ts index bd3f09b..bd04ad5 100644 --- a/web/src/views/market-overview/useMarketOverview.ts +++ b/web/src/views/market-overview/useMarketOverview.ts @@ -7,6 +7,9 @@ import type { IndicesResponse, OverviewModuleStatus, SectorsResponse, + SectorRankingsResponse, + CapitalFlowResponse, + TurnoverTrendResponse, ShortTermConclusionResponse, ShortTermEmotionResponse, SummaryResponse, @@ -19,7 +22,10 @@ type MarketResponse = | SectorsResponse | FundsResponse | ShortTermEmotionResponse - | ShortTermConclusionResponse; + | ShortTermConclusionResponse + | SectorRankingsResponse + | CapitalFlowResponse + | TurnoverTrendResponse; export interface OverviewModuleState { status: OverviewModuleStatus; @@ -54,6 +60,9 @@ export function useMarketOverview( const breadth = ref>(createState()); const sectors = ref>(createState()); const funds = ref>(createState()); + const sectorRankings = ref>(createState()); + const capitalFlow = ref>(createState()); + const turnoverTrend = ref>(createState()); const shortTermEmotion = ref>(createState()); const shortTermConclusion = ref>(createState()); const analysisHorizon = ref(readPreference('a-share-analysis:overview-horizon', '综合')); @@ -65,6 +74,9 @@ export function useMarketOverview( breadth.value, sectors.value, funds.value, + sectorRankings.value, + capitalFlow.value, + turnoverTrend.value, shortTermEmotion.value, shortTermConclusion.value, ].some((module) => module.status === 'loading'), @@ -76,6 +88,9 @@ export function useMarketOverview( breadth.value.status = 'loading'; sectors.value.status = 'loading'; funds.value.status = 'loading'; + sectorRankings.value.status = 'loading'; + capitalFlow.value.status = 'loading'; + turnoverTrend.value.status = 'loading'; shortTermEmotion.value.status = 'loading'; shortTermConclusion.value.status = 'loading'; } @@ -101,6 +116,13 @@ export function useMarketOverview( loadModule(breadth, api.getBreadth), loadModule(sectors, api.getSectors), loadModule(funds, api.getFunds), + loadModule(sectorRankings, () => + api.getSectorRankings({ category: 'industry', sort: 'rise', limit: 10 }), + ), + loadModule(capitalFlow, () => + api.getCapitalFlow({ category: 'industry', sort: 'inflow', limit: 10 }), + ), + loadModule(turnoverTrend, () => api.getTurnoverTrend({ interval: '5' })), loadModule(shortTermEmotion, api.getShortTermEmotion), loadModule(shortTermConclusion, api.getShortTermConclusion), ]); @@ -122,6 +144,9 @@ export function useMarketOverview( breadth, sectors, funds, + sectorRankings, + capitalFlow, + turnoverTrend, shortTermEmotion, shortTermConclusion, isLoading,