feat(FEAT-MO-005): 接入市场结构前端数据层
This commit is contained in:
@@ -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',
|
||||
]);
|
||||
|
||||
@@ -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<SummaryResponse>({ method: 'get', url: '/api/v1/market-overview/summary' }),
|
||||
getIndices: () =>
|
||||
|
||||
@@ -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<RequestClient>[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' },
|
||||
}),
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -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<SectorRankingsResponse>({
|
||||
method: 'get',
|
||||
url: '/api/v1/market-overview/sector-rankings',
|
||||
params,
|
||||
}),
|
||||
getCapitalFlow: (params: CapitalFlowQuery) =>
|
||||
requestClient<CapitalFlowResponse>({
|
||||
method: 'get',
|
||||
url: '/api/v1/market-overview/capital-flow',
|
||||
params,
|
||||
}),
|
||||
getTurnoverTrend: (params: TurnoverTrendQuery) =>
|
||||
requestClient<TurnoverTrendResponse>({
|
||||
method: 'get',
|
||||
url: '/api/v1/market-overview/turnover-trend',
|
||||
params,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const marketStructureApi = createMarketStructureApi();
|
||||
|
||||
export default marketStructureApi;
|
||||
@@ -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<SummaryResponse['data']>;
|
||||
export type IndicesData = NonNullable<IndicesResponse['data']>;
|
||||
export type BreadthData = NonNullable<BreadthResponse['data']>;
|
||||
@@ -14,5 +17,8 @@ export type SectorsData = NonNullable<SectorsResponse['data']>;
|
||||
export type FundsData = NonNullable<FundsResponse['data']>;
|
||||
export type ShortTermEmotionData = NonNullable<ShortTermEmotionResponse['data']>;
|
||||
export type ShortTermConclusionData = NonNullable<ShortTermConclusionResponse['data']>;
|
||||
export type SectorRankingsData = NonNullable<SectorRankingsResponse['data']>;
|
||||
export type CapitalFlowData = NonNullable<CapitalFlowResponse['data']>;
|
||||
export type TurnoverTrendData = NonNullable<TurnoverTrendResponse['data']>;
|
||||
|
||||
export type OverviewModuleStatus = 'idle' | 'loading' | 'ready' | 'error';
|
||||
|
||||
@@ -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('分析偏好写入本地存储并可恢复', () => {
|
||||
|
||||
@@ -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<TResponse extends MarketResponse> {
|
||||
status: OverviewModuleStatus;
|
||||
@@ -54,6 +60,9 @@ export function useMarketOverview(
|
||||
const breadth = ref<OverviewModuleState<BreadthResponse>>(createState());
|
||||
const sectors = ref<OverviewModuleState<SectorsResponse>>(createState());
|
||||
const funds = ref<OverviewModuleState<FundsResponse>>(createState());
|
||||
const sectorRankings = ref<OverviewModuleState<SectorRankingsResponse>>(createState());
|
||||
const capitalFlow = ref<OverviewModuleState<CapitalFlowResponse>>(createState());
|
||||
const turnoverTrend = ref<OverviewModuleState<TurnoverTrendResponse>>(createState());
|
||||
const shortTermEmotion = ref<OverviewModuleState<ShortTermEmotionResponse>>(createState());
|
||||
const shortTermConclusion = ref<OverviewModuleState<ShortTermConclusionResponse>>(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,
|
||||
|
||||
Reference in New Issue
Block a user