fix:优化数据

This commit is contained in:
丹尼尔
2026-03-15 16:38:59 +08:00
parent a609f81a36
commit 3aa1a586e5
43 changed files with 14565 additions and 294 deletions

View File

@@ -18,17 +18,20 @@ export interface CustomerRead {
id: number;
name: string;
contact_info: string | null;
tags: string | null;
created_at: string;
}
export interface CustomerCreate {
name: string;
contact_info?: string | null;
tags?: string | null;
}
export interface CustomerUpdate {
name?: string;
contact_info?: string | null;
tags?: string | null;
}
export interface RequirementAnalyzeRequest {
@@ -69,7 +72,68 @@ export interface FinanceSyncResult {
}
export interface FinanceSyncResponse {
items: FinanceSyncResult[];
status: string;
new_files: number;
details: FinanceSyncResult[];
}
export interface FinanceRecordRead {
id: number;
month: string;
type: string;
file_name: string;
file_path: string;
amount: number | null;
billing_date: string | null;
created_at: string;
}
export interface TemplateInfo {
name: string;
type: "excel" | "word";
size: number;
uploaded_at: number;
}
export interface AIConfig {
id?: string;
name?: string;
provider: string;
api_key: string;
base_url: string;
model_name: string;
temperature: number;
system_prompt_override: string;
}
export interface AIConfigListItem {
id: string;
name: string;
provider: string;
model_name: string;
base_url: string;
api_key_configured: boolean;
is_active: boolean;
}
export interface AIConfigCreate {
name?: string;
provider?: string;
api_key?: string;
base_url?: string;
model_name?: string;
temperature?: number;
system_prompt_override?: string;
}
export interface AIConfigUpdate {
name?: string;
provider?: string;
api_key?: string;
base_url?: string;
model_name?: string;
temperature?: number;
system_prompt_override?: string;
}
export interface ProjectRead {
@@ -84,6 +148,7 @@ export interface ProjectRead {
}
export interface ProjectUpdate {
raw_requirement?: string | null;
ai_solution_md?: string | null;
status?: string;
}
@@ -112,10 +177,15 @@ async function request<T>(
if (!res.ok) {
const text = await res.text();
let detail = text;
let detail: string = text;
try {
const j = JSON.parse(text);
detail = j.detail ?? text;
const raw = j.detail ?? text;
if (Array.isArray(raw) && raw.length > 0) {
detail = raw.map((x: { msg?: string }) => x.msg ?? JSON.stringify(x)).join("; ");
} else {
detail = typeof raw === "string" ? raw : JSON.stringify(raw);
}
} catch {
// keep text
}
@@ -176,7 +246,10 @@ export async function downloadFileAsBlob(
// --------------- Customers ---------------
export const customersApi = {
list: () => request<CustomerRead[]>("/customers/"),
list: (params?: { q?: string }) =>
request<CustomerRead[]>(
params?.q ? `/customers/?q=${encodeURIComponent(params.q)}` : "/customers/"
),
get: (id: number) => request<CustomerRead>(`/customers/${id}`),
create: (body: CustomerCreate) =>
request<CustomerRead>("/customers/", {
@@ -207,10 +280,11 @@ export const projectsApi = {
body: JSON.stringify(body),
}),
generateQuote: (projectId: number) =>
request<QuoteGenerateResponse>(`/projects/${projectId}/generate_quote`, {
method: "POST",
}),
generateQuote: (projectId: number, template?: string | null) =>
request<QuoteGenerateResponse>(
`/projects/${projectId}/generate_quote${template ? `?template=${encodeURIComponent(template)}` : ""}`,
{ method: "POST" }
),
generateContract: (projectId: number, body: ContractGenerateRequest) =>
request<ContractGenerateResponse>(
@@ -222,20 +296,285 @@ export const projectsApi = {
),
};
export async function listProjects(): Promise<ProjectRead[]> {
return request<ProjectRead[]>("/projects/");
export async function listProjects(params?: {
customer_tag?: string;
}): Promise<ProjectRead[]> {
const searchParams: Record<string, string> = {};
if (params?.customer_tag?.trim()) searchParams.customer_tag = params.customer_tag.trim();
return request<ProjectRead[]>("/projects/", {
params: searchParams,
});
}
export async function getProject(projectId: number): Promise<ProjectRead> {
return request<ProjectRead>(`/projects/${projectId}`);
}
// --------------- Settings / Templates ---------------
export const templatesApi = {
list: () => request<TemplateInfo[]>("/settings/templates"),
upload: async (file: File): Promise<{ name: string; path: string }> => {
const base = apiBase();
const formData = new FormData();
formData.append("file", file);
const res = await fetch(`${base}/settings/templates/upload`, {
method: "POST",
body: formData,
});
if (!res.ok) {
const text = await res.text();
let detail = text;
try {
const j = JSON.parse(text);
detail = j.detail ?? text;
} catch {
// keep text
}
throw new Error(detail);
}
return res.json();
},
};
// --------------- AI Settings ---------------
export const aiSettingsApi = {
get: () => request<AIConfig>("/settings/ai"),
list: () => request<AIConfigListItem[]>("/settings/ai/list"),
getById: (id: string) => request<AIConfig>(`/settings/ai/${id}`),
create: (body: AIConfigCreate) =>
request<AIConfig>("/settings/ai", {
method: "POST",
body: JSON.stringify(body),
}),
update: (id: string, body: AIConfigUpdate) =>
request<AIConfig>(`/settings/ai/${id}`, {
method: "PUT",
body: JSON.stringify(body),
}),
delete: (id: string) =>
request<void>(`/settings/ai/${id}`, { method: "DELETE" }),
activate: (id: string) =>
request<AIConfig>(`/settings/ai/${id}/activate`, { method: "POST" }),
test: (configId?: string) =>
request<{ status: string; message: string }>(
configId ? `/settings/ai/test?config_id=${encodeURIComponent(configId)}` : "/settings/ai/test",
{ method: "POST" }
),
};
// --------------- Email Configs (multi-account sync) ---------------
export interface EmailConfigRead {
id: string;
host: string;
port: number;
user: string;
mailbox: string;
active: boolean;
}
export interface EmailConfigCreate {
host: string;
port?: number;
user: string;
password: string;
mailbox?: string;
active?: boolean;
}
export interface EmailConfigUpdate {
host?: string;
port?: number;
user?: string;
password?: string;
mailbox?: string;
active?: boolean;
}
export interface EmailFolder {
raw: string;
decoded: string;
}
export const emailConfigsApi = {
list: () => request<EmailConfigRead[]>("/settings/email"),
create: (body: EmailConfigCreate) =>
request<EmailConfigRead>("/settings/email", {
method: "POST",
body: JSON.stringify(body),
}),
update: (id: string, body: EmailConfigUpdate) =>
request<EmailConfigRead>(`/settings/email/${id}`, {
method: "PUT",
body: JSON.stringify(body),
}),
delete: (id: string) =>
request<void>(`/settings/email/${id}`, { method: "DELETE" }),
/** List mailbox folders for an account (to pick custom label). Use decoded as mailbox value. */
listFolders: (configId: string) =>
request<{ folders: EmailFolder[] }>(`/settings/email/${configId}/folders`),
};
// --------------- Cloud Docs (快捷入口) ---------------
export interface CloudDocLinkRead {
id: string;
name: string;
url: string;
}
export interface CloudDocLinkCreate {
name: string;
url: string;
}
export interface CloudDocLinkUpdate {
name?: string;
url?: string;
}
export const cloudDocsApi = {
list: () => request<CloudDocLinkRead[]>("/settings/cloud-docs"),
create: (body: CloudDocLinkCreate) =>
request<CloudDocLinkRead>("/settings/cloud-docs", {
method: "POST",
body: JSON.stringify(body),
}),
update: (id: string, body: CloudDocLinkUpdate) =>
request<CloudDocLinkRead>(`/settings/cloud-docs/${id}`, {
method: "PUT",
body: JSON.stringify(body),
}),
delete: (id: string) =>
request<void>(`/settings/cloud-docs/${id}`, { method: "DELETE" }),
};
// --------------- Cloud Doc Config (API 凭证) ---------------
export interface CloudDocConfigRead {
feishu: { app_id: string; app_secret_configured: boolean };
yuque: { token_configured: boolean; default_repo: string };
tencent: { client_id: string; client_secret_configured: boolean };
}
export interface CloudDocConfigUpdate {
feishu?: { app_id?: string; app_secret?: string };
yuque?: { token?: string; default_repo?: string };
tencent?: { client_id?: string; client_secret?: string };
}
export const cloudDocConfigApi = {
get: () => request<CloudDocConfigRead>("/settings/cloud-doc-config"),
update: (body: CloudDocConfigUpdate) =>
request<CloudDocConfigRead>("/settings/cloud-doc-config", {
method: "PUT",
body: JSON.stringify(body),
}),
};
// --------------- Push to Cloud ---------------
export interface PushToCloudRequest {
platform: "feishu" | "yuque" | "tencent";
title?: string;
body_md?: string;
}
export interface PushToCloudResponse {
url: string;
cloud_doc_id: string;
}
export function pushProjectToCloud(
projectId: number,
body: PushToCloudRequest
): Promise<PushToCloudResponse> {
return request<PushToCloudResponse>(`/projects/${projectId}/push-to-cloud`, {
method: "POST",
body: JSON.stringify(body),
});
}
// --------------- Portal Links (快捷门户) ---------------
export interface PortalLinkRead {
id: string;
name: string;
url: string;
}
export interface PortalLinkCreate {
name: string;
url: string;
}
export interface PortalLinkUpdate {
name?: string;
url?: string;
}
export const portalLinksApi = {
list: () => request<PortalLinkRead[]>("/settings/portal-links"),
create: (body: PortalLinkCreate) =>
request<PortalLinkRead>("/settings/portal-links", {
method: "POST",
body: JSON.stringify(body),
}),
update: (id: string, body: PortalLinkUpdate) =>
request<PortalLinkRead>(`/settings/portal-links/${id}`, {
method: "PUT",
body: JSON.stringify(body),
}),
delete: (id: string) =>
request<void>(`/settings/portal-links/${id}`, { method: "DELETE" }),
};
// --------------- Finance ---------------
export const financeApi = {
sync: () =>
request<FinanceSyncResponse>("/finance/sync", { method: "POST" }),
/** Upload invoice (PDF/image); returns created record with AI-extracted amount/date. */
uploadInvoice: async (file: File): Promise<FinanceRecordRead> => {
const base = apiBase();
const formData = new FormData();
formData.append("file", file);
const res = await fetch(`${base}/finance/upload`, {
method: "POST",
body: formData,
});
if (!res.ok) {
const text = await res.text();
let detail = text;
try {
const j = JSON.parse(text);
detail = j.detail ?? text;
} catch {
// keep text
}
throw new Error(detail);
}
return res.json();
},
/** Update amount/billing_date of a record (e.g. after manual review). */
updateRecord: (id: number, body: { amount?: number | null; billing_date?: string | null }) =>
request<FinanceRecordRead>(`/finance/records/${id}`, {
method: "PATCH",
body: JSON.stringify(body),
}),
/** List distinct months with records (YYYY-MM). */
listMonths: () => request<string[]>("/finance/months"),
/** List records for a month (YYYY-MM). */
listRecords: (month: string) =>
request<FinanceRecordRead[]>(`/finance/records?month=${encodeURIComponent(month)}`),
/** Returns the URL to download the zip (or use downloadFile with the path). */
getDownloadUrl: (month: string) => `${apiBase()}/finance/download/${month}`,