feat(selection): 支持选股结果服务端分页
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
const requestJson = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock("@/shared/api/request-json", () => ({ requestJson }))
|
||||
|
||||
import { getSelectionResults, getSelectionRun } from "./selection.api"
|
||||
|
||||
describe("selection API adapters", () => {
|
||||
beforeEach(() => {
|
||||
requestJson.mockReset()
|
||||
})
|
||||
|
||||
it("sends paged result filters using the HTTP field names", async () => {
|
||||
await getSelectionResults("zhixing_b1", "2026-08-08", {
|
||||
category: "pullback",
|
||||
page: 2,
|
||||
pageSize: 15,
|
||||
search: "平安银行",
|
||||
})
|
||||
|
||||
const [input, init] = requestJson.mock.calls[0] as [
|
||||
string,
|
||||
{ signal?: AbortSignal },
|
||||
]
|
||||
const params = new URL(input, "http://localhost").searchParams
|
||||
expect(params.get("strategy")).toBe("zhixing_b1")
|
||||
expect(params.get("target_trade_date")).toBe("2026-08-08")
|
||||
expect(params.get("page")).toBe("2")
|
||||
expect(params.get("page_size")).toBe("15")
|
||||
expect(params.get("search")).toBe("平安银行")
|
||||
expect(params.get("category")).toBe("pullback")
|
||||
expect(init).toEqual({ signal: undefined })
|
||||
})
|
||||
|
||||
it("uses the same query contract while polling a run", async () => {
|
||||
await getSelectionRun("run-1", { page: 3, pageSize: 5 })
|
||||
|
||||
const [input] = requestJson.mock.calls[0] as [string]
|
||||
const params = new URL(input, "http://localhost").searchParams
|
||||
expect(input).toContain("/api/v1/selection/runs/run-1?")
|
||||
expect(params.get("page")).toBe("3")
|
||||
expect(params.get("page_size")).toBe("5")
|
||||
expect(params.has("search")).toBe(false)
|
||||
expect(params.has("category")).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -2,6 +2,7 @@ import { requestJson } from "@/shared/api/request-json"
|
||||
|
||||
import type {
|
||||
SelectionResults,
|
||||
SelectionResultsQuery,
|
||||
SelectionRunAccepted,
|
||||
SelectionRunRequest,
|
||||
SelectionStrategy,
|
||||
@@ -10,9 +11,11 @@ import type {
|
||||
export function getSelectionResults(
|
||||
strategy: SelectionStrategy,
|
||||
targetTradeDate?: string,
|
||||
query: SelectionResultsQuery = { page: 1, pageSize: 10 },
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
const params = new URLSearchParams({ strategy })
|
||||
const params = buildSelectionQueryParams(query)
|
||||
params.set("strategy", strategy)
|
||||
if (targetTradeDate) params.set("target_trade_date", targetTradeDate)
|
||||
return requestJson<SelectionResults>(
|
||||
`/api/v1/selection/results?${params.toString()}`,
|
||||
@@ -20,10 +23,28 @@ export function getSelectionResults(
|
||||
)
|
||||
}
|
||||
|
||||
export function getSelectionRun(runId: string, signal?: AbortSignal) {
|
||||
return requestJson<SelectionResults>(`/api/v1/selection/runs/${runId}`, {
|
||||
signal,
|
||||
export function getSelectionRun(
|
||||
runId: string,
|
||||
query: SelectionResultsQuery = { page: 1, pageSize: 10 },
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
const params = buildSelectionQueryParams(query)
|
||||
return requestJson<SelectionResults>(
|
||||
`/api/v1/selection/runs/${runId}?${params.toString()}`,
|
||||
{
|
||||
signal,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
function buildSelectionQueryParams(query: SelectionResultsQuery) {
|
||||
const params = new URLSearchParams({
|
||||
page: String(query.page),
|
||||
page_size: String(query.pageSize),
|
||||
})
|
||||
if (query.search) params.set("search", query.search)
|
||||
if (query.category) params.set("category", query.category)
|
||||
return params
|
||||
}
|
||||
|
||||
export function triggerSelectionRun(
|
||||
|
||||
@@ -11,33 +11,63 @@ import {
|
||||
getSelectionRun,
|
||||
triggerSelectionRun,
|
||||
} from "./selection.api"
|
||||
import type { SelectionRunRequest, SelectionStrategy } from "./selection.types"
|
||||
import type {
|
||||
SelectionResultsQuery,
|
||||
SelectionRunRequest,
|
||||
SelectionStrategy,
|
||||
} from "./selection.types"
|
||||
|
||||
export const selectionResultsQueryKey = (
|
||||
strategy: SelectionStrategy,
|
||||
targetTradeDate?: string,
|
||||
) => ["selection", "results", strategy, targetTradeDate] as const
|
||||
query: SelectionResultsQuery = { page: 1, pageSize: 10 },
|
||||
) =>
|
||||
[
|
||||
"selection",
|
||||
"results",
|
||||
strategy,
|
||||
targetTradeDate,
|
||||
query.page,
|
||||
query.pageSize,
|
||||
query.search ?? "",
|
||||
query.category ?? "all",
|
||||
] as const
|
||||
|
||||
export const selectionRunQueryKey = (runId: string) =>
|
||||
["selection", "runs", runId] as const
|
||||
export const selectionRunQueryKey = (
|
||||
runId: string,
|
||||
query: SelectionResultsQuery = { page: 1, pageSize: 10 },
|
||||
) =>
|
||||
[
|
||||
"selection",
|
||||
"runs",
|
||||
runId,
|
||||
query.page,
|
||||
query.pageSize,
|
||||
query.search ?? "",
|
||||
query.category ?? "all",
|
||||
] as const
|
||||
|
||||
export function useSelectionResults(
|
||||
strategy: SelectionStrategy,
|
||||
targetTradeDate?: string,
|
||||
query: SelectionResultsQuery = { page: 1, pageSize: 10 },
|
||||
) {
|
||||
return useQuery({
|
||||
queryFn: ({ signal }) =>
|
||||
getSelectionResults(strategy, targetTradeDate, signal),
|
||||
queryKey: selectionResultsQueryKey(strategy, targetTradeDate),
|
||||
getSelectionResults(strategy, targetTradeDate, query, signal),
|
||||
queryKey: selectionResultsQueryKey(strategy, targetTradeDate, query),
|
||||
})
|
||||
}
|
||||
|
||||
export function useSelectionRun(runId: string | null) {
|
||||
export function useSelectionRun(
|
||||
runId: string | null,
|
||||
resultQuery: SelectionResultsQuery = { page: 1, pageSize: 10 },
|
||||
) {
|
||||
const queryClient = useQueryClient()
|
||||
const query = useQuery({
|
||||
enabled: Boolean(runId),
|
||||
queryFn: ({ signal }) => getSelectionRun(runId ?? "", signal),
|
||||
queryKey: selectionRunQueryKey(runId ?? "none"),
|
||||
queryFn: ({ signal }) => getSelectionRun(runId ?? "", resultQuery, signal),
|
||||
queryKey: selectionRunQueryKey(runId ?? "none", resultQuery),
|
||||
refetchInterval: (query) =>
|
||||
query.state.data?.status === "running" ? 1500 : false,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
export type SelectionStrategy = "zhixing_b1"
|
||||
|
||||
export type SelectionCategoryFilter =
|
||||
"all" | "pullback" | "oversold" | "original"
|
||||
|
||||
export const selectionCategoryFilters = [
|
||||
"all",
|
||||
"pullback",
|
||||
"oversold",
|
||||
"original",
|
||||
] as const
|
||||
|
||||
export interface SelectionResultsQuery {
|
||||
page: number
|
||||
pageSize: number
|
||||
search?: string
|
||||
category?: Exclude<SelectionCategoryFilter, "all">
|
||||
}
|
||||
|
||||
export type SelectionRunStatus =
|
||||
"no_data" | "running" | "success" | "partial_success" | "failed"
|
||||
|
||||
@@ -50,6 +67,9 @@ export interface SelectionResults {
|
||||
error_message: string | null
|
||||
created_at: string | null
|
||||
finished_at: string | null
|
||||
page: number
|
||||
page_size: number
|
||||
signals_total: number
|
||||
failures: SelectionFailure[]
|
||||
signals: SelectionSignal[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user