- packages/api-client:从 openapi.yaml 生成类型;cookie + CSRF / Bearer 两种鉴权、 分片上传、fetch 读事件流(API2) - apps/web:Vue 3 + Vite + TanStack Query,样式只来自 tokens.css / components.css(AR10); 扣点说明放在「开始解析」旁边(红线 9);接口地址、路由方式运行时可配(架构 §5.2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
4.0 KiB
TypeScript
93 lines
4.0 KiB
TypeScript
/** 服务端数据:全部由 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<Pick<Schemas["ChecklistItemFields"], "categories" | "title" | "importance">>;
|
||
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<string | undefined>) {
|
||
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<string | undefined>) {
|
||
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<Schemas["JobStatus"]> = new Set(["succeeded", "failed", "cancelled"]);
|
||
|
||
/** 解析进度:事件流推送为主(lib/events.ts 直接改这条缓存),每 5 秒轮询兜底,结束后停止轮询。 */
|
||
export function useParseJob(id: MaybeRefOrGetter<string>) {
|
||
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<string>) {
|
||
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<string>) {
|
||
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[];
|
||
}>,
|
||
});
|
||
}
|