修复报销附件上传与中文文件名(07-28-travel-reimbursement-guide)

这个提交包含在:
2026-07-30 14:41:08 +08:00
父节点 531d2846a6
当前提交 734153847b
共修改 15 个文件,包含 434 行新增和 65 行删除
+6
查看文件
@@ -359,6 +359,12 @@ export function mapApiErrorMessage(
return "附件超过大小限制,请压缩后重新上传。";
}
if (details?.file === "too_many_files") {
return details?.maxFiles === 5
? "单次最多上传 5 个附件,请分批上传。"
: "一次选择的附件过多,请减少数量后分批上传。";
}
if (details?.file === "dangerous_filename") {
return "附件名称不安全,请重命名后重新上传。";
}
+2
查看文件
@@ -490,6 +490,8 @@ export async function deleteReimbursementItem(reimbursementId: string, itemId: s
);
}
export const MAX_REIMBURSEMENT_ATTACHMENT_FILES = 5;
export async function uploadReimbursementAttachments(reimbursementId: string, files: File[]) {
const formData = new FormData();
for (const file of files) {
@@ -20,6 +20,7 @@ import {
deleteReimbursementItem,
downloadReimbursementAttachment,
fetchReimbursementDetail,
MAX_REIMBURSEMENT_ATTACHMENT_FILES,
precheckReimbursement,
submitReimbursement,
updateReimbursementItem,
@@ -678,6 +679,9 @@ export function MobileReimbursementDetailPage() {
if (!files.length) {
throw new Error("请选择附件材料");
}
if (files.length > MAX_REIMBURSEMENT_ATTACHMENT_FILES) {
throw new Error(`单次最多上传 ${MAX_REIMBURSEMENT_ATTACHMENT_FILES} 个附件,请分批上传。`);
}
return uploadReimbursementAttachments(reimbursementId, files);
},
onSuccess: async () => {
@@ -1088,7 +1092,7 @@ export function MobileReimbursementDetailPage() {
<div className="flex items-center justify-between gap-3">
<div>
<h2 className="text-base font-semibold text-zinc-900">附件材料</h2>
<p className="mt-1 text-xs text-zinc-500">合同、订单、付款截图或情况说明</p>
<p className="mt-1 text-xs text-zinc-500">PDF/JPG/PNG/WebP,单次最多 5 个</p>
</div>
{canManageAttachments ? (
<Button
@@ -1110,7 +1114,16 @@ export function MobileReimbursementDetailPage() {
type="file"
accept="application/pdf,image/jpeg,image/png,image/webp"
multiple
onChange={(event) => setSelectedAttachmentFiles(Array.from(event.target.files ?? []))}
onChange={(event) => {
const files = Array.from(event.target.files ?? []);
if (files.length > MAX_REIMBURSEMENT_ATTACHMENT_FILES) {
setSelectedAttachmentFiles([]);
event.target.value = "";
toast.error(`单次最多上传 ${MAX_REIMBURSEMENT_ATTACHMENT_FILES} 个附件,请分批上传。`);
return;
}
setSelectedAttachmentFiles(files);
}}
/>
{selectedAttachmentFiles.length ? (
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs text-zinc-600">
+15 -2
查看文件
@@ -13,6 +13,7 @@ import {
deleteReimbursementItem,
downloadReimbursementAttachment,
fetchReimbursementDetail,
MAX_REIMBURSEMENT_ATTACHMENT_FILES,
precheckReimbursement,
submitReimbursement,
updateReimbursementItem,
@@ -662,6 +663,9 @@ export function ReimbursementDetailPage() {
if (!files.length) {
throw new Error("请选择附件材料");
}
if (files.length > MAX_REIMBURSEMENT_ATTACHMENT_FILES) {
throw new Error(`单次最多上传 ${MAX_REIMBURSEMENT_ATTACHMENT_FILES} 个附件,请分批上传。`);
}
return uploadReimbursementAttachments(reimbursementId, files);
},
onSuccess: async () => {
@@ -1496,7 +1500,7 @@ export function ReimbursementDetailPage() {
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div className="space-y-1">
<h3 className="text-base font-semibold text-zinc-900">补充附件</h3>
<p className="text-xs text-zinc-500">合同、订单、支付截图或情况说明;敏感附件下载会要求填写用途。</p>
<p className="text-xs text-zinc-500">合同、订单、支付截图或情况说明;PDF/JPG/PNG/WebP,单次最多 5 个。</p>
</div>
{canEditItems ? (
<div className="flex flex-col gap-2 md:flex-row md:items-center">
@@ -1516,7 +1520,16 @@ export function ReimbursementDetailPage() {
type="file"
accept="application/pdf,image/jpeg,image/png,image/webp"
multiple
onChange={(event) => setSelectedAttachmentFiles(Array.from(event.target.files ?? []))}
onChange={(event) => {
const files = Array.from(event.target.files ?? []);
if (files.length > MAX_REIMBURSEMENT_ATTACHMENT_FILES) {
setSelectedAttachmentFiles([]);
event.target.value = "";
toast.error(`单次最多上传 ${MAX_REIMBURSEMENT_ATTACHMENT_FILES} 个附件,请分批上传。`);
return;
}
setSelectedAttachmentFiles(files);
}}
/>
<Button
type="button"