forked from wangziqi/gongxue-base
- 编辑旧消息会删除其后全部消息时,先弹确认告知影响范围 - 有待发送附件时切换会话,先确认再丢弃(避免静默删除已上传文件) - EditableCell 保存成功后提供 6 秒内"撤销"入口(把旧值重新保存), 重新编辑时自动清除 - 导入向导/重新上传显示上传进度条与百分比,不再"假死" - AI 附件上传透传进度给附件列表 - 租赁合同上传显示按钮 loading 与上传百分比
85 lines
2.8 KiB
TypeScript
85 lines
2.8 KiB
TypeScript
import api from './index';
|
||
import { validateResponse } from '../utils/validate';
|
||
import { importRunEnvelopeSchema } from './schemas';
|
||
import type {
|
||
ImportPreviewResult,
|
||
ImportReceipt,
|
||
ImportRunDetail,
|
||
ImportStageRequest,
|
||
} from '../components/ImportWizard/types';
|
||
|
||
interface ApiEnvelope<T> {
|
||
success: boolean;
|
||
data: T;
|
||
message?: string;
|
||
}
|
||
|
||
export async function createImportRun(
|
||
file: File,
|
||
options: {
|
||
source: 'ai' | 'manual';
|
||
conversationId?: number;
|
||
stages?: ImportStageRequest[];
|
||
mapping?: Record<string, Record<string, string>>;
|
||
/** 上传进度回调(0-100) */
|
||
onProgress?: (percent: number) => void;
|
||
},
|
||
): Promise<ImportRunDetail> {
|
||
const form = new FormData();
|
||
form.append('file', file);
|
||
form.append('source', options.source);
|
||
if (options.conversationId) form.append('conversationId', String(options.conversationId));
|
||
if (options.stages?.length) form.append('stages', JSON.stringify(options.stages));
|
||
if (options.mapping && Object.keys(options.mapping).length > 0) {
|
||
form.append('mapping', JSON.stringify(options.mapping));
|
||
}
|
||
const res = await api.post<ApiEnvelope<ImportRunDetail>>('/imports/runs', form, {
|
||
onUploadProgress: (event) => {
|
||
if (!options.onProgress || !event.total) return;
|
||
options.onProgress(Math.min(Math.round((event.loaded / event.total) * 100), 100));
|
||
},
|
||
});
|
||
return res.data;
|
||
}
|
||
|
||
export async function getImportRun(runId: string): Promise<ImportRunDetail> {
|
||
const res = await api.get<ApiEnvelope<ImportRunDetail>>(
|
||
`/imports/runs/${encodeURIComponent(runId)}`,
|
||
);
|
||
return validateResponse<ApiEnvelope<ImportRunDetail>>(importRunEnvelopeSchema, res).data;
|
||
}
|
||
|
||
export async function previewImportStep(
|
||
runId: string,
|
||
stepKey: string,
|
||
body: { sheets?: string[]; mapping?: Record<string, string> },
|
||
): Promise<ImportPreviewResult> {
|
||
const res = await api.post<ApiEnvelope<ImportPreviewResult>>(
|
||
`/imports/runs/${encodeURIComponent(runId)}/steps/${encodeURIComponent(stepKey)}/preview`,
|
||
body,
|
||
);
|
||
return res.data;
|
||
}
|
||
|
||
export async function commitImportStep(
|
||
runId: string,
|
||
stepKey: string,
|
||
decisions: Array<{ rowId: number; action: 'create' | 'update' | 'skip' }>,
|
||
): Promise<ImportReceipt> {
|
||
const res = await api.post<ApiEnvelope<ImportReceipt>>(
|
||
`/imports/runs/${encodeURIComponent(runId)}/steps/${encodeURIComponent(stepKey)}/commit`,
|
||
{ decisions },
|
||
);
|
||
return res.data;
|
||
}
|
||
|
||
export function importErrorReportUrl(runId: string, stepKey?: string): string {
|
||
const base = import.meta.env.PROD
|
||
? '/api'
|
||
: `http://localhost:${import.meta.env.VITE_API_PORT || 3002}/api`;
|
||
const params = new URLSearchParams();
|
||
if (stepKey) params.set('stepKey', stepKey);
|
||
const query = params.toString();
|
||
return `${base}/imports/runs/${encodeURIComponent(runId)}/report${query ? `?${query}` : ''}`;
|
||
}
|