import { apiFetch, apiFetchBlob, downloadBlob } from "@/lib/api"; /** 统一分页响应(后端 utils.pagination.pagination_result)。 */ export interface Page { items: T[]; total: number; page: number; page_size: number; total_pages: number; } export interface TodoItem { id: number; todo_type: string; customer_id: number | null; priority: string; status: string; due_at: string | null; } export interface DashboardData { todos?: { pending?: number; items?: TodoItem[] }; overview?: { total_customers?: number; signed_customers?: number; closed_customers?: number; churn_rate?: number; total_aum?: string; reports_sent?: number; reports_draft?: number; visits_count?: number; }; customer_levels?: Record; } export interface Customer { customer_id: number; real_name: string | null; phone: string | null; risk_level: string | null; customer_level: string | null; relation_status: string; total_assets: number | null; } export interface CustomerDetail extends Customer { risk_score?: number | null; signed_time?: string | null; assigned_time?: string | null; } export interface Holding { product_id: number; product_code: string | null; product_name: string | null; risk_level: string | null; shares: string; cost_amount: string; current_value: string; profit_loss: string; profit_ratio: string; status: string; } export interface ReportItem { report_id: string; intent: string | null; title: string | null; send_status: string; send_time: string | null; } export interface Visit { id: number; customer_id: number; advisor_id: number; visit_type: string; visit_time: string; summary: string | null; audio_url: string | null; create_time: string | null; } export interface Diagnosis { total_value: string; allocation: Record; concentration: { top_product_ratio: number; top_product: { product_name: string | null } | null }; holdings: { product_code: string | null; product_name: string | null; product_type: string; current_value: string }[]; } export interface RiskAlert { id: number; customer_id: number | null; order_id: number | null; alert_type: string; alert_level: string; trigger_detail: string | null; transaction_ids: unknown; confidence: number | null; status: string; handler_id: number | null; handle_result: string | null; handle_time: string | null; create_time: string | null; } export interface WorkOrder { id: number; work_order_no: string; order_type: string; sub_type: string | null; customer_id: number | null; submitter_id: number | null; handler_id: number | null; current_node: string | null; priority: string; status: string; biz_content: Record | null; create_time: string | null; update_time: string | null; } export interface FundProduct { id: number; product_code: string; product_name: string; product_type: string; risk_level: string; nav: number | null; nav_date: string | null; fund_manager: string | null; status: string; } export interface Draft { draft_id?: string; id?: string; title?: string; content?: string; status?: string; customer_id?: number | null; intent?: string | null; create_time?: string | null; update_time?: string | null; } export interface TalkTemplate { scene: string; content: string; } export interface PersonalReport { total_aum: string; total_customers: number; signed_customers: number; reports_sent: number; visits_count: number; } export interface AuditRow { id: number; username: string | null; module: string | null; action: string | null; target: string | null; detail: string | null; status: string | null; create_time: string | null; } /** GET /products/{code}/history —— 走势图数据(series=nav|return)。 */ export interface FundNavHistory { product_code: string; series: string; total_growth_rate: number; max_return_rate: number; max_drawdown_rate: number; items: { nav_date: string; nav?: number | null; return_rate?: number | null }[]; } /** POST /advisor/data-query —— 工作台代理的 NL2SQL 查询结果。 */ export interface AdvisorDataQueryResult { query_id?: string; answer?: string; summary?: string; columns?: string[]; rows?: Record[]; row_count?: number; truncated?: boolean; [key: string]: unknown; } /** GET /profile/composed —— 画像 + 记忆 + 持仓三源整合结果。 */ export interface ComposedProfile { customer_id?: number; profile?: Record | null; memories?: unknown[]; holdings?: unknown[]; [key: string]: unknown; } /** GET /health/ready —— 四库就绪探测。 */ export interface HealthReady { ready?: boolean; checks?: Record; [key: string]: unknown; } function qs(params: Record) { const search = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { if (value !== undefined && value !== null && value !== "") search.set(key, String(value)); } const text = search.toString(); return text ? `?${text}` : ""; } export const adminApi = { me: () => apiFetch<{ user: import("@/types/api").UserPayload }>("/auth/me").then((r) => r.user), dashboard: () => apiFetch("/advisor/dashboard"), personalReport: () => apiFetch("/advisor/report/personal"), customers: (params: { status?: string; keyword?: string; page?: number; page_size?: number }) => apiFetch>(`/advisor/customers${qs(params)}`), customer: (id: number, unmask = false) => apiFetch(`/advisor/customers/${id}${qs({ unmask: unmask ? "true" : null })}`), holdings: (id: number) => apiFetch(`/advisor/customers/${id}/holdings`), reports: (id: number, page = 1, page_size = 10) => apiFetch>(`/advisor/customers/${id}/reports${qs({ page, page_size })}`), diagnosis: (id: number) => apiFetch(`/advisor/diagnosis/${id}`), setRelation: (id: number, action: "sign" | "close", reason?: string) => apiFetch<{ customer_id: number; status: string }>(`/advisor/customers/${id}/relation`, { method: "POST", body: JSON.stringify({ action, reason: reason || null }), }), todos: (params: { status?: string; todo_type?: string; page?: number; page_size?: number }) => apiFetch>(`/advisor/todos${qs(params)}`), handleTodo: (id: number, action: "process" | "done") => apiFetch(`/advisor/todos/${id}/handle`, { method: "POST", body: JSON.stringify({ action }), }), visits: (params: { customer_id?: number; page?: number; page_size?: number }) => apiFetch>(`/advisor/visits${qs(params)}`), createVisit: (body: { customer_id: number; visit_type: string; visit_time: string; summary?: string }) => apiFetch<{ visit_id: number }>("/advisor/visits", { method: "POST", body: JSON.stringify(body) }), updateVisit: (id: number, body: Partial<{ visit_type: string; visit_time: string; summary: string }>) => apiFetch(`/advisor/visits/${id}`, { method: "PUT", body: JSON.stringify(body) }), talkTemplates: () => apiFetch("/advisor/talk-templates"), touchLogs: (customerId: number, page = 1, page_size = 10) => apiFetch<{ messages: unknown[]; visits: Visit[] }>(`/advisor/touch-logs${qs({ customer_id: customerId, page, page_size })}`), drafts: (params: { customer_id?: number; status?: string; page?: number; page_size?: number } = {}) => apiFetch>(`/advisor/drafts${qs(params)}`), draft: (draftId: string) => apiFetch(`/advisor/drafts/${draftId}`), saveDraft: (draftId: string, body: { title?: string; content?: string }) => apiFetch(`/advisor/drafts/${draftId}/save`, { method: "PUT", body: JSON.stringify(body) }), discardDraft: (draftId: string) => apiFetch(`/advisor/drafts/${draftId}/discard`, { method: "POST" }), sendDraft: (draftId: string) => apiFetch(`/advisor/drafts/${draftId}/send`, { method: "POST" }), runRebalance: (query: string) => apiFetch("/advisor/rebalance/run", { method: "POST", body: JSON.stringify({ query }) }), generateTalkScript: (query: string) => apiFetch<{ script?: string; content?: string }>("/advisor/talk-script", { method: "POST", body: JSON.stringify({ query }), }), strategies: () => apiFetch<{ risk_level: string; target_allocation: unknown; drift_threshold: number }[]>("/advisor/strategies"), funds: (params: { keyword?: string; product_type?: string; risk_level?: string; page?: number; page_size?: number }) => apiFetch>(`/advisor/funds${qs(params)}`), auditLedger: (params: { page?: number; page_size?: number; action?: string } = {}) => apiFetch>(`/advisor/audit/ledger${qs(params)}`), alerts: (params: { status?: string; page?: number; page_size?: number } = {}) => apiFetch>(`/risk/alert/list${qs(params)}`), alertAction: (id: number, action: "release" | "block" | "freeze") => apiFetch>(`/risk/alert/${id}/${action}`, { method: "POST" }), workOrders: (params: { status?: string; page?: number; page_size?: number } = {}) => apiFetch>(`/work-order/list${qs(params)}`), workOrder: (id: number) => apiFetch(`/work-order/${id}`), claimWorkOrder: (id: number) => apiFetch("/work-order/claim", { method: "POST", body: JSON.stringify({ work_order_id: id }) }), submitWorkOrder: (id: number) => apiFetch("/work-order/submit-review", { method: "POST", body: JSON.stringify({ work_order_id: id }) }), reviewWorkOrder: (id: number, approve: boolean, comment: string) => apiFetch("/work-order/review", { method: "POST", body: JSON.stringify({ work_order_id: id, approve, comment }), }), products: (params: { keyword?: string; page?: number; page_size?: number } = {}) => apiFetch>(`/products${qs(params)}`), /** GET /products/{product_code}/history —— 历史净值/收益序列,series 仅支持 nav|return。 */ productHistory: ( productCode: string, params: { start_date?: string; end_date?: string; series?: "nav" | "return" } = {} ) => apiFetch(`/products/${encodeURIComponent(productCode)}/history${qs(params)}`), /** POST /advisor/data-query —— 工作台侧代理的 NL2SQL 客户数据查询。 */ advisorDataQuery: (body: { query: string; session_id?: string; max_rows?: number; page?: number; page_size?: number; }) => apiFetch("/advisor/data-query", { method: "POST", body: JSON.stringify(body), }), /** GET /advisor/visits/{id} —— 回访详情(列表接口之外的单条读取)。 */ visit: (visitId: number) => apiFetch(`/advisor/visits/${visitId}`), /** GET /advisor/audit/export —— 导出本人审计台账 CSV。 */ exportAuditLedger: async (params: { action?: string } = {}) => { const blob = await apiFetchBlob(`/advisor/audit/export${qs(params)}`); downloadBlob(blob, `audit-ledger-${new Date().toISOString().slice(0, 10)}.csv`); }, /** GET /profile/composed —— 画像 + 记忆 + 持仓三源整合,仅员工可查。 */ composedProfile: (customerId: number) => apiFetch(`/profile/composed${qs({ customer_id: customerId })}`), /** GET /health/ready —— 四库就绪探测。 */ healthReady: () => apiFetch("/health/ready"), }; /** 统一错误信息提取,避免每个组件重复判断。 */ export function errMessage(error: unknown, fallback = "操作失败") { return error instanceof Error ? error.message : fallback; }