修复 R124 企业微信移动入口与部署迁移门禁

这个提交包含在:
2026-05-08 14:09:04 +08:00
父节点 377c76b5aa
当前提交 b78e82edab
共修改 14 个文件,包含 1375 行新增和 32 行删除
+73 -5
查看文件
@@ -4,12 +4,14 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import {
createReimbursement,
deleteReimbursement,
fetchReimbursements,
type ReimbursementListItem,
type ReimbursementStatus,
} from "../api/reimbursements";
import { useAuth } from "../auth/AuthContext";
import { hasPermission } from "../auth/permissions";
import { ConfirmDialog } from "../components/common/ConfirmDialog";
import { Button } from "../components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs";
import { PageHeader } from "../components/common/PageHeader";
@@ -48,7 +50,17 @@ function formatDate(value: string | null | undefined) {
return value || "未填写日期";
}
function ReimbursementCard({ item }: { item: ReimbursementListItem }) {
function ReimbursementCard({
item,
canDeleteDraft,
isDeleting,
onDelete,
}: {
item: ReimbursementListItem;
canDeleteDraft: boolean;
isDeleting: boolean;
onDelete: (item: ReimbursementListItem) => void;
}) {
const actionLabel = item.status === "draft" || item.status === "rejected" ? "继续编辑" : "查看详情";
const displayCode = resolveReimbursementDisplayCode(item);
@@ -75,9 +87,23 @@ function ReimbursementCard({ item }: { item: ReimbursementListItem }) {
{item.description ? <p className="mt-3 line-clamp-2 text-sm text-zinc-500">{item.description}</p> : null}
<Button asChild className="mt-4 w-full" type="button">
<Link to={`/mobile/reimbursements/${item.id}`}>{actionLabel}</Link>
</Button>
<div className={canDeleteDraft ? "mt-4 grid grid-cols-2 gap-2" : "mt-4"}>
<Button asChild className="w-full" type="button">
<Link to={`/mobile/reimbursements/${item.id}`}>{actionLabel}</Link>
</Button>
{canDeleteDraft ? (
<Button
className="w-full"
data-testid="mobile-reimbursement-delete"
disabled={isDeleting}
onClick={() => onDelete(item)}
type="button"
variant="destructive"
>
删除草稿
</Button>
) : null}
</div>
</article>
);
}
@@ -87,7 +113,9 @@ export function MobileReimbursementsPage() {
const queryClient = useQueryClient();
const { permissions } = useAuth();
const [status, setStatus] = useState<ReimbursementStatus>("draft");
const [deleteTarget, setDeleteTarget] = useState<ReimbursementListItem | null>(null);
const canCreateExpense = hasPermission(permissions, "expense.create");
const canDeleteExpense = hasPermission(permissions, "expense.delete_own");
const listQuery = useQuery({
queryKey: ["mobile", "reimbursements", status],
@@ -106,6 +134,21 @@ export function MobileReimbursementsPage() {
},
});
const deleteMutation = useMutation({
mutationFn: (reimbursementId: string) => deleteReimbursement(reimbursementId),
onSuccess: async () => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["mobile", "reimbursements"] }),
queryClient.invalidateQueries({ queryKey: ["reimbursements"] }),
]);
setDeleteTarget(null);
toast.success("草稿已删除");
},
onError: (error) => {
toast.error(resolveErrorMessage(error));
},
});
const items = listQuery.data?.items ?? [];
const total = listQuery.data?.total ?? 0;
const totalAmount = useMemo(
@@ -166,10 +209,35 @@ export function MobileReimbursementsPage() {
<div className="rounded-lg border border-dashed border-zinc-200 p-6 text-center text-sm text-zinc-500">暂无报销记录</div>
) : null}
{items.map((item) => (
<ReimbursementCard item={item} key={item.id} />
<ReimbursementCard
canDeleteDraft={canDeleteExpense && item.status === "draft"}
isDeleting={deleteMutation.isPending}
item={item}
key={item.id}
onDelete={setDeleteTarget}
/>
))}
</div>
</section>
<ConfirmDialog
open={Boolean(deleteTarget)}
onOpenChange={(open) => {
if (!open) {
setDeleteTarget(null);
}
}}
title="删除草稿?"
description="删除后无法恢复,已添加发票会回到可用状态。"
variant="destructive"
confirmLabel="删除草稿"
onConfirm={async () => {
if (!deleteTarget) {
return;
}
await deleteMutation.mutateAsync(deleteTarget.id);
}}
/>
</PageScaffold>
);
}