Files
platforms/frontend/platform_admin/src/api/contract-attachment.ts
czl231 99324acc89 feat: 新增配送合同附件安全上传功能
- 增加合同专用 PDF 上传、鉴权预览和失败清理接口
- 支持草稿附件替换移除、并发保护和启用完整性校验
- 修复循环模板引用导致文件选择器无法打开的问题
- 补充专项测试、中文项目文档和操作日志
2026-08-12 21:38:49 +08:00

75 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 功能:配送合同 PDF 附件的上传、失败清理与鉴权预览客户端。
* 版本v1.0.0
*/
import { getToken } from '@/utils/auth';
const platformApiBaseURL =
import.meta.env.VITE_API_BASE_URL ||
'http://localhost:12426/heqi/platform/v1';
export type ContractAttachmentMetadata = {
has_file: boolean;
available: boolean;
requires_reupload: boolean;
display_name: string;
version: string;
};
export type ContractAttachmentUploadReply = {
receipt: string;
cleanup_token: string;
display_name: string;
size: number;
};
type ApiEnvelope<T> = { code?: number; message?: string; details?: T };
/** 返回当前登录凭证请求头。 */
function authorizationHeaders(): Record<string, string> {
const token = getToken();
return token ? { Authorization: token } : {};
}
/** 上传经过前端预检的单个 PDF服务端仍会验证真实内容。 */
async function upload(file: File): Promise<ContractAttachmentUploadReply> {
const form = new FormData();
form.append('file', file);
const response = await fetch(
`${platformApiBaseURL}/gasorder_contract/attachment/upload`,
{ method: 'POST', headers: authorizationHeaders(), body: form },
);
const payload = (await response.json()) as ApiEnvelope<ContractAttachmentUploadReply>;
if (!response.ok || payload.code !== 0 || !payload.details) {
throw new Error(payload.message || '合同附件上传失败');
}
return payload.details;
}
/** 使用受签名保护的清理凭证删除尚未绑定的临时文件。 */
async function cleanup(cleanupToken: string): Promise<void> {
const response = await fetch(
`${platformApiBaseURL}/gasorder_contract/attachment/cleanup`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authorizationHeaders() },
body: JSON.stringify({ cleanup_token: cleanupToken }),
},
);
if (!response.ok) throw new Error('临时合同附件清理失败');
const payload = (await response.json()) as ApiEnvelope<{ deleted: boolean }>;
if (payload.code !== 0) throw new Error(payload.message || '临时合同附件清理失败');
}
/** 获取鉴权 PDF Blob内部存储 URI 始终不会暴露给浏览器。 */
async function load(identity: string): Promise<Blob> {
const response = await fetch(
`${platformApiBaseURL}/gasorder_contract/${encodeURIComponent(identity)}/attachment`,
{ headers: authorizationHeaders() },
);
if (!response.ok) throw new Error('合同附件不可用,请重新上传');
return response.blob();
}
export const contractAttachmentApi = { upload, cleanup, load };