R92 A3-A4 优化发票上传状态与批量上传

这个提交包含在:
2026-05-05 22:16:35 +08:00
父节点 abba3371dd
当前提交 9d8c7e0478
共修改 6 个文件,包含 385 行新增和 122 行删除
@@ -1,26 +1,36 @@
import type { UseMutationResult, UseQueryResult } from "@tanstack/react-query";
import type { FetchMyDelegationsResponse } from "../../api/delegations";
import { Button } from "../../components/ui/button";
export type InvoiceUploadCardProps = {
uploadMutation: UseMutationResult<{ invoicePoolId: string; status: string; fileSha256: string }, Error, void>;
selectedFile: File | null;
setSelectedFile: (file: File | null) => void;
selectedFiles: File[];
setSelectedFiles: (files: File[]) => void;
isUploading: boolean;
uploadProgressLabel: string;
onUpload: () => void;
showProxyOwnerSelector: boolean;
proxyDelegationsQuery: UseQueryResult<FetchMyDelegationsResponse, Error>;
proxyDelegationsQuery: { data?: FetchMyDelegationsResponse };
uploadOwnerEmployeeId: string;
setUploadOwnerEmployeeId: (id: string) => void;
};
export function InvoiceUploadCard({
uploadMutation,
selectedFile,
setSelectedFile,
selectedFiles,
setSelectedFiles,
isUploading,
uploadProgressLabel,
onUpload,
showProxyOwnerSelector,
proxyDelegationsQuery,
uploadOwnerEmployeeId,
setUploadOwnerEmployeeId,
}: InvoiceUploadCardProps) {
const selectedLabel =
selectedFiles.length === 0
? "选择文件"
: selectedFiles.length === 1
? selectedFiles[0].name
: `已选择 ${selectedFiles.length} 个文件`;
return (
<div className="card-std p-6 space-y-4">
<h3 className="card-title">上传票据(VAT OCR)</h3>
@@ -47,27 +57,39 @@ export function InvoiceUploadCard({
className="cursor-pointer inline-flex h-9 items-center justify-center rounded-md border border-dashed border-zinc-300 bg-zinc-50 px-4 py-2 text-sm font-medium text-zinc-600 shadow-sm transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-within:ring-1 focus-within:ring-zinc-400"
>
<span className="material-symbols-outlined mr-2 text-[18px]">cloud_upload</span>
{selectedFile ? selectedFile.name : "选择文件"}
{selectedLabel}
</label>
<input
id="file-upload"
className="hidden"
accept="application/pdf,image/jpeg,image/png,image/webp"
data-testid="invoice-upload-file"
onChange={(event) => setSelectedFile(event.target.files?.[0] ?? null)}
multiple
onChange={(event) => setSelectedFiles(Array.from(event.target.files ?? []))}
type="file"
/>
<Button
data-testid="invoice-upload-submit"
className={selectedFile ? "" : "opacity-50"}
disabled={uploadMutation.isPending || !selectedFile}
onClick={() => uploadMutation.mutate()}
className={selectedFiles.length ? "" : "opacity-50"}
disabled={isUploading || selectedFiles.length === 0}
onClick={onUpload}
type="button"
>
{uploadMutation.isPending ? "上传中..." : "上传并触发 OCR"}
{isUploading ? uploadProgressLabel || "上传中..." : "上传并触发 OCR"}
</Button>
</div>
{selectedFiles.length ? (
<div className="rounded-xl border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs text-zinc-600">
{selectedFiles.slice(0, 4).map((file) => (
<div key={`${file.name}-${file.size}`} className="truncate">
{file.name} · {Math.ceil(file.size / 1024)} KB
</div>
))}
{selectedFiles.length > 4 ? <div>另有 {selectedFiles.length - 4} 个文件</div> : null}
</div>
) : null}
</div>
);
}