实现企微原生材料预览(07-27-yang-jun-reimbursement-readiness)
这个提交包含在:
@@ -7,6 +7,7 @@ import {
|
||||
archiveInvoice,
|
||||
bindInvoice,
|
||||
fetchBindableInvoices,
|
||||
fetchInvoiceSignedUrl,
|
||||
fetchInvoicePoolList,
|
||||
unbindInvoice,
|
||||
uploadInvoice,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
deleteReimbursement,
|
||||
deleteReimbursementItem,
|
||||
downloadReimbursementAttachment,
|
||||
fetchReimbursementAttachmentPreviewUrl,
|
||||
fetchReimbursementDetail,
|
||||
MAX_REIMBURSEMENT_ATTACHMENT_FILES,
|
||||
precheckReimbursement,
|
||||
@@ -65,6 +67,8 @@ import { TEST_IDS } from "../lib/testIds";
|
||||
import { withUploadTimeout } from "../lib/uploadTimeout";
|
||||
import { useInvoiceOcrTracker } from "../hooks/useInvoiceOcrTracker";
|
||||
import { cn } from "../lib/utils";
|
||||
import { isWecomWebView } from "../lib/runtimeContext";
|
||||
import { openFileInBrowser, previewFileInWecom } from "../lib/wecomFilePreview";
|
||||
|
||||
type ItemFormState = {
|
||||
mode: "create" | "edit";
|
||||
@@ -678,10 +682,37 @@ export function MobileReimbursementDetailPage() {
|
||||
|
||||
const { requestDownloadReason, downloadReasonDialog } = useAttachmentDownloadReasonDialog();
|
||||
|
||||
const openInvoiceMutation = useMutation({
|
||||
mutationFn: async (invoiceId: string) => {
|
||||
const result = await fetchInvoiceSignedUrl(invoiceId);
|
||||
const openedInWecom = await previewFileInWecom({
|
||||
url: result.signedUrl,
|
||||
name: result.originalName,
|
||||
size: result.fileSize,
|
||||
});
|
||||
if (!openedInWecom) {
|
||||
openFileInBrowser(result.signedUrl);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(resolveErrorMessage(error));
|
||||
},
|
||||
});
|
||||
|
||||
const openAttachmentMutation = useMutation({
|
||||
mutationFn: ({ attachmentId, reason }: { attachmentId: string; reason?: string }) =>
|
||||
downloadReimbursementAttachment(reimbursementId, attachmentId, reason),
|
||||
mutationFn: async ({ attachment, reason }: {
|
||||
attachment: { id: string; originalName: string; fileSize: number } & AttachmentGovernanceFields;
|
||||
reason?: string;
|
||||
}) => {
|
||||
if (isWecomWebView()) {
|
||||
const result = await fetchReimbursementAttachmentPreviewUrl(reimbursementId, attachment.id, reason);
|
||||
await previewFileInWecom({ url: result.signedUrl, name: result.originalName, size: result.fileSize });
|
||||
return null;
|
||||
}
|
||||
return downloadReimbursementAttachment(reimbursementId, attachment.id, reason);
|
||||
},
|
||||
onSuccess: (result) => {
|
||||
if (!result) return;
|
||||
const url = URL.createObjectURL(result.blob);
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
@@ -691,12 +722,12 @@ export function MobileReimbursementDetailPage() {
|
||||
},
|
||||
});
|
||||
|
||||
async function handleOpenAttachment(attachment: { id: string; originalName?: string } & AttachmentGovernanceFields) {
|
||||
async function handleOpenAttachment(attachment: { id: string; originalName: string; fileSize: number } & AttachmentGovernanceFields) {
|
||||
const reason = await requestDownloadReason(attachment);
|
||||
if (reason === null) {
|
||||
return;
|
||||
}
|
||||
openAttachmentMutation.mutate({ attachmentId: attachment.id, reason });
|
||||
openAttachmentMutation.mutate({ attachment, reason });
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
@@ -1028,6 +1059,16 @@ export function MobileReimbursementDetailPage() {
|
||||
移除
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
className="mt-3 w-full"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
type="button"
|
||||
disabled={openInvoiceMutation.isPending}
|
||||
onClick={() => openInvoiceMutation.mutate(invoice.id)}
|
||||
>
|
||||
查看原件
|
||||
</Button>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
@@ -1117,7 +1158,7 @@ export function MobileReimbursementDetailPage() {
|
||||
disabled={openAttachmentMutation.isPending}
|
||||
onClick={() => void handleOpenAttachment(attachment)}
|
||||
>
|
||||
查看
|
||||
查看原件
|
||||
</Button>
|
||||
{canManageAttachments ? (
|
||||
<Button
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
deleteReimbursement,
|
||||
deleteReimbursementItem,
|
||||
downloadReimbursementAttachment,
|
||||
fetchReimbursementAttachmentPreviewUrl,
|
||||
fetchReimbursementDetail,
|
||||
MAX_REIMBURSEMENT_ATTACHMENT_FILES,
|
||||
precheckReimbursement,
|
||||
@@ -26,10 +27,13 @@ import { remindWorkflowTask } from "../api/workflow";
|
||||
import {
|
||||
bindInvoice,
|
||||
fetchBindableInvoices,
|
||||
fetchInvoiceSignedUrl,
|
||||
unbindInvoice,
|
||||
uploadInvoice,
|
||||
} from "../api/invoicePool";
|
||||
import { toast } from "sonner";
|
||||
import { isWecomWebView } from "../lib/runtimeContext";
|
||||
import { openFileInBrowser, previewFileInWecom } from "../lib/wecomFilePreview";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
import { hasPermission } from "../auth/permissions";
|
||||
import { CardSkeleton } from "../components/common/CardSkeleton";
|
||||
@@ -674,10 +678,37 @@ export function ReimbursementDetailPage() {
|
||||
|
||||
const { requestDownloadReason, downloadReasonDialog } = useAttachmentDownloadReasonDialog();
|
||||
|
||||
const openInvoiceMutation = useMutation({
|
||||
mutationFn: async (invoiceId: string) => {
|
||||
const result = await fetchInvoiceSignedUrl(invoiceId);
|
||||
const openedInWecom = await previewFileInWecom({
|
||||
url: result.signedUrl,
|
||||
name: result.originalName,
|
||||
size: result.fileSize,
|
||||
});
|
||||
if (!openedInWecom) {
|
||||
openFileInBrowser(result.signedUrl);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(buildActionErrorMessage(error, "打开发票失败"));
|
||||
},
|
||||
});
|
||||
|
||||
const openAttachmentMutation = useMutation({
|
||||
mutationFn: ({ attachmentId, reason }: { attachmentId: string; reason?: string }) =>
|
||||
downloadReimbursementAttachment(reimbursementId, attachmentId, reason),
|
||||
mutationFn: async ({ attachment, reason }: {
|
||||
attachment: { id: string; originalName: string; fileSize: number } & AttachmentGovernanceFields;
|
||||
reason?: string;
|
||||
}) => {
|
||||
if (isWecomWebView()) {
|
||||
const result = await fetchReimbursementAttachmentPreviewUrl(reimbursementId, attachment.id, reason);
|
||||
await previewFileInWecom({ url: result.signedUrl, name: result.originalName, size: result.fileSize });
|
||||
return null;
|
||||
}
|
||||
return downloadReimbursementAttachment(reimbursementId, attachment.id, reason);
|
||||
},
|
||||
onSuccess: (result) => {
|
||||
if (!result) return;
|
||||
const url = URL.createObjectURL(result.blob);
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
@@ -687,12 +718,12 @@ export function ReimbursementDetailPage() {
|
||||
},
|
||||
});
|
||||
|
||||
async function handleOpenAttachment(attachment: { id: string; originalName?: string } & AttachmentGovernanceFields) {
|
||||
async function handleOpenAttachment(attachment: { id: string; originalName: string; fileSize: number } & AttachmentGovernanceFields) {
|
||||
const reason = await requestDownloadReason(attachment);
|
||||
if (reason === null) {
|
||||
return;
|
||||
}
|
||||
openAttachmentMutation.mutate({ attachmentId: attachment.id, reason });
|
||||
openAttachmentMutation.mutate({ attachment, reason });
|
||||
}
|
||||
|
||||
if (!reimbursementId) {
|
||||
@@ -1423,6 +1454,15 @@ export function ReimbursementDetailPage() {
|
||||
<span className="badge-neutral">
|
||||
{resolveInvoiceStatusLabel(item.status)}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={openInvoiceMutation.isPending}
|
||||
onClick={() => openInvoiceMutation.mutate(item.id)}
|
||||
>
|
||||
查看原件
|
||||
</Button>
|
||||
{canUnbindInvoices ? (
|
||||
<Button
|
||||
type="button"
|
||||
@@ -1515,7 +1555,7 @@ export function ReimbursementDetailPage() {
|
||||
disabled={openAttachmentMutation.isPending}
|
||||
onClick={() => void handleOpenAttachment(attachment)}
|
||||
>
|
||||
查看
|
||||
查看原件
|
||||
</Button>
|
||||
{canEditItems ? (
|
||||
<Button
|
||||
|
||||
在新工单中引用
屏蔽一个用户