/** 服务端数据:全部由 TanStack Query 管理,不放进 Pinia(架构 §4.2)。 */ import { unwrap, type Schemas } from "@bid-doctor/api-client"; import { useQuery } from "@tanstack/vue-query"; import { computed, toValue, type MaybeRefOrGetter } from "vue"; import { api } from "./api"; import { qk } from "./keys"; export type Me = Schemas["Me"]; export type Quota = Schemas["QuotaOverview"]; export type Plan = Schemas["Plan"]; export type ProjectListItem = Schemas["ProjectListItem"]; export type ProjectDetail = Schemas["ProjectDetail"]; export type DocumentInfo = Schemas["Document"]; export type ParseJob = Schemas["ParseJob"]; export type Checklist = Schemas["Checklist"]; /** * 契约里 ChecklistItem 把 categories、title、importance 列为必填,但它们定义在 allOf 的 ChecklistItemFields 里, * openapi-typescript 生成的类型仍是可选。这里按契约的 required 收窄,不改字段本身。 */ export type ChecklistItem = Schemas["ChecklistItem"] & Required>; export type Citation = Schemas["Citation"]; export type MarkingRule = Schemas["MarkingRule"]; export type SessionInfo = Schemas["SessionInfo"]; export const fetchMe = () => unwrap(api.GET("/me")); export function useMe() { return useQuery({ queryKey: qk.me, queryFn: fetchMe, staleTime: 60_000 }); } export function useQuota() { return useQuery({ queryKey: qk.quota, queryFn: () => unwrap(api.GET("/quota")) }); } /** 某个额度单位(如 parse)的本期账户;没有这个单位时为 undefined */ export function useQuotaAccount(unit: Schemas["QuotaUnit"]) { const q = useQuota(); return computed(() => q.data.value?.accounts.find((a) => a.unit === unit)); } export function usePlans() { return useQuery({ queryKey: qk.plans, queryFn: () => unwrap(api.GET("/plans")), staleTime: 10 * 60_000 }); } export function useProjects() { // 骨架阶段只取第一页(默认按截止时间排序);项目多了再接游标分页 return useQuery({ queryKey: qk.projects, queryFn: () => unwrap(api.GET("/projects", { params: { query: { limit: 100 } } })) }); } export function useProject(id: MaybeRefOrGetter) { return useQuery({ queryKey: computed(() => qk.project(toValue(id) ?? "")), queryFn: () => unwrap(api.GET("/projects/{project_id}", { params: { path: { project_id: toValue(id)! } } })), enabled: computed(() => !!toValue(id)), }); } export function useDocument(id: MaybeRefOrGetter) { return useQuery({ queryKey: computed(() => qk.document(toValue(id) ?? "")), queryFn: () => unwrap(api.GET("/documents/{document_id}", { params: { path: { document_id: toValue(id)! } } })), enabled: computed(() => !!toValue(id)), }); } export const TERMINAL_JOB: ReadonlySet = new Set(["succeeded", "failed", "cancelled"]); /** 解析进度:事件流推送为主(lib/events.ts 直接改这条缓存),每 5 秒轮询兜底,结束后停止轮询。 */ export function useParseJob(id: MaybeRefOrGetter) { return useQuery({ queryKey: computed(() => qk.parseJob(toValue(id))), queryFn: () => unwrap(api.GET("/parse-jobs/{job_id}", { params: { path: { job_id: toValue(id) } } })), refetchInterval: (query) => (query.state.data && TERMINAL_JOB.has(query.state.data.status) ? false : 5000), }); } export function useChecklist(projectId: MaybeRefOrGetter) { return useQuery({ queryKey: computed(() => qk.checklist(toValue(projectId))), queryFn: () => unwrap(api.GET("/projects/{project_id}/checklist", { params: { path: { project_id: toValue(projectId) } } })), }); } export function useChecklistItems(projectId: MaybeRefOrGetter) { return useQuery({ queryKey: computed(() => qk.checklistItems(toValue(projectId))), queryFn: () => unwrap(api.GET("/projects/{project_id}/checklist/items", { params: { path: { project_id: toValue(projectId) } } })) as Promise<{ items: ChecklistItem[]; }>, }); }