feat(FEAT-MO-005): 接入市场结构前端数据层
This commit is contained in:
@@ -17,6 +17,9 @@ describe('市场概览业务 API', () => {
|
|||||||
await api.getBreadth();
|
await api.getBreadth();
|
||||||
await api.getSectors();
|
await api.getSectors();
|
||||||
await api.getFunds();
|
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.getShortTermEmotion();
|
||||||
await api.getShortTermConclusion();
|
await api.getShortTermConclusion();
|
||||||
|
|
||||||
@@ -26,6 +29,9 @@ describe('市场概览业务 API', () => {
|
|||||||
'/api/v1/market-overview/breadth',
|
'/api/v1/market-overview/breadth',
|
||||||
'/api/v1/market-overview/sectors',
|
'/api/v1/market-overview/sectors',
|
||||||
'/api/v1/market-overview/funds',
|
'/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/emotion',
|
||||||
'/api/v1/market-overview/short-term/conclusion',
|
'/api/v1/market-overview/short-term/conclusion',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ import type {
|
|||||||
ShortTermEmotionResponse,
|
ShortTermEmotionResponse,
|
||||||
SummaryResponse,
|
SummaryResponse,
|
||||||
} from '@/types/market-overview';
|
} from '@/types/market-overview';
|
||||||
|
import { createMarketStructureApi } from './structure';
|
||||||
|
|
||||||
export function createMarketOverviewApi(requestClient: RequestClient = request) {
|
export function createMarketOverviewApi(requestClient: RequestClient = request) {
|
||||||
return {
|
return {
|
||||||
|
...createMarketStructureApi(requestClient),
|
||||||
getSummary: () =>
|
getSummary: () =>
|
||||||
requestClient<SummaryResponse>({ method: 'get', url: '/api/v1/market-overview/summary' }),
|
requestClient<SummaryResponse>({ method: 'get', url: '/api/v1/market-overview/summary' }),
|
||||||
getIndices: () =>
|
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 FundsResponse = components['schemas']['FundsResponseDto'];
|
||||||
export type ShortTermEmotionResponse = components['schemas']['ShortTermEmotionResponseDto'];
|
export type ShortTermEmotionResponse = components['schemas']['ShortTermEmotionResponseDto'];
|
||||||
export type ShortTermConclusionResponse = components['schemas']['ShortTermConclusionResponseDto'];
|
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 SummaryData = NonNullable<SummaryResponse['data']>;
|
||||||
export type IndicesData = NonNullable<IndicesResponse['data']>;
|
export type IndicesData = NonNullable<IndicesResponse['data']>;
|
||||||
export type BreadthData = NonNullable<BreadthResponse['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 FundsData = NonNullable<FundsResponse['data']>;
|
||||||
export type ShortTermEmotionData = NonNullable<ShortTermEmotionResponse['data']>;
|
export type ShortTermEmotionData = NonNullable<ShortTermEmotionResponse['data']>;
|
||||||
export type ShortTermConclusionData = NonNullable<ShortTermConclusionResponse['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';
|
export type OverviewModuleStatus = 'idle' | 'loading' | 'ready' | 'error';
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ describe('市场概览状态', () => {
|
|||||||
getBreadth: vi.fn().mockResolvedValue(response),
|
getBreadth: vi.fn().mockResolvedValue(response),
|
||||||
getSectors: vi.fn().mockResolvedValue(response),
|
getSectors: vi.fn().mockResolvedValue(response),
|
||||||
getFunds: 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),
|
getShortTermEmotion: vi.fn().mockResolvedValue(response),
|
||||||
getShortTermConclusion: vi.fn().mockResolvedValue(response),
|
getShortTermConclusion: vi.fn().mockResolvedValue(response),
|
||||||
};
|
};
|
||||||
@@ -32,6 +35,9 @@ describe('市场概览状态', () => {
|
|||||||
expect(state.isLoading.value).toBe(false);
|
expect(state.isLoading.value).toBe(false);
|
||||||
expect(state.shortTermEmotion.value.status).toBe('ready');
|
expect(state.shortTermEmotion.value.status).toBe('ready');
|
||||||
expect(state.shortTermConclusion.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('分析偏好写入本地存储并可恢复', () => {
|
it('分析偏好写入本地存储并可恢复', () => {
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import type {
|
|||||||
IndicesResponse,
|
IndicesResponse,
|
||||||
OverviewModuleStatus,
|
OverviewModuleStatus,
|
||||||
SectorsResponse,
|
SectorsResponse,
|
||||||
|
SectorRankingsResponse,
|
||||||
|
CapitalFlowResponse,
|
||||||
|
TurnoverTrendResponse,
|
||||||
ShortTermConclusionResponse,
|
ShortTermConclusionResponse,
|
||||||
ShortTermEmotionResponse,
|
ShortTermEmotionResponse,
|
||||||
SummaryResponse,
|
SummaryResponse,
|
||||||
@@ -19,7 +22,10 @@ type MarketResponse =
|
|||||||
| SectorsResponse
|
| SectorsResponse
|
||||||
| FundsResponse
|
| FundsResponse
|
||||||
| ShortTermEmotionResponse
|
| ShortTermEmotionResponse
|
||||||
| ShortTermConclusionResponse;
|
| ShortTermConclusionResponse
|
||||||
|
| SectorRankingsResponse
|
||||||
|
| CapitalFlowResponse
|
||||||
|
| TurnoverTrendResponse;
|
||||||
|
|
||||||
export interface OverviewModuleState<TResponse extends MarketResponse> {
|
export interface OverviewModuleState<TResponse extends MarketResponse> {
|
||||||
status: OverviewModuleStatus;
|
status: OverviewModuleStatus;
|
||||||
@@ -54,6 +60,9 @@ export function useMarketOverview(
|
|||||||
const breadth = ref<OverviewModuleState<BreadthResponse>>(createState());
|
const breadth = ref<OverviewModuleState<BreadthResponse>>(createState());
|
||||||
const sectors = ref<OverviewModuleState<SectorsResponse>>(createState());
|
const sectors = ref<OverviewModuleState<SectorsResponse>>(createState());
|
||||||
const funds = ref<OverviewModuleState<FundsResponse>>(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 shortTermEmotion = ref<OverviewModuleState<ShortTermEmotionResponse>>(createState());
|
||||||
const shortTermConclusion = ref<OverviewModuleState<ShortTermConclusionResponse>>(createState());
|
const shortTermConclusion = ref<OverviewModuleState<ShortTermConclusionResponse>>(createState());
|
||||||
const analysisHorizon = ref(readPreference('a-share-analysis:overview-horizon', '综合'));
|
const analysisHorizon = ref(readPreference('a-share-analysis:overview-horizon', '综合'));
|
||||||
@@ -65,6 +74,9 @@ export function useMarketOverview(
|
|||||||
breadth.value,
|
breadth.value,
|
||||||
sectors.value,
|
sectors.value,
|
||||||
funds.value,
|
funds.value,
|
||||||
|
sectorRankings.value,
|
||||||
|
capitalFlow.value,
|
||||||
|
turnoverTrend.value,
|
||||||
shortTermEmotion.value,
|
shortTermEmotion.value,
|
||||||
shortTermConclusion.value,
|
shortTermConclusion.value,
|
||||||
].some((module) => module.status === 'loading'),
|
].some((module) => module.status === 'loading'),
|
||||||
@@ -76,6 +88,9 @@ export function useMarketOverview(
|
|||||||
breadth.value.status = 'loading';
|
breadth.value.status = 'loading';
|
||||||
sectors.value.status = 'loading';
|
sectors.value.status = 'loading';
|
||||||
funds.value.status = 'loading';
|
funds.value.status = 'loading';
|
||||||
|
sectorRankings.value.status = 'loading';
|
||||||
|
capitalFlow.value.status = 'loading';
|
||||||
|
turnoverTrend.value.status = 'loading';
|
||||||
shortTermEmotion.value.status = 'loading';
|
shortTermEmotion.value.status = 'loading';
|
||||||
shortTermConclusion.value.status = 'loading';
|
shortTermConclusion.value.status = 'loading';
|
||||||
}
|
}
|
||||||
@@ -101,6 +116,13 @@ export function useMarketOverview(
|
|||||||
loadModule(breadth, api.getBreadth),
|
loadModule(breadth, api.getBreadth),
|
||||||
loadModule(sectors, api.getSectors),
|
loadModule(sectors, api.getSectors),
|
||||||
loadModule(funds, api.getFunds),
|
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(shortTermEmotion, api.getShortTermEmotion),
|
||||||
loadModule(shortTermConclusion, api.getShortTermConclusion),
|
loadModule(shortTermConclusion, api.getShortTermConclusion),
|
||||||
]);
|
]);
|
||||||
@@ -122,6 +144,9 @@ export function useMarketOverview(
|
|||||||
breadth,
|
breadth,
|
||||||
sectors,
|
sectors,
|
||||||
funds,
|
funds,
|
||||||
|
sectorRankings,
|
||||||
|
capitalFlow,
|
||||||
|
turnoverTrend,
|
||||||
shortTermEmotion,
|
shortTermEmotion,
|
||||||
shortTermConclusion,
|
shortTermConclusion,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
|||||||
Reference in New Issue
Block a user