MIF7 移动信息架构整改收口

这个提交包含在:
2026-05-13 00:02:21 +08:00
父节点 39a930399f
当前提交 c83dbb7376
共修改 10 个文件,包含 691 行新增和 206 行删除
+40 -5
查看文件
@@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useEffect, useMemo, useRef, useState } from "react";
import { Link, useLocation, useNavigate, useSearchParams } from "react-router-dom";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import {
@@ -35,6 +35,10 @@ const STATUS_LABEL: Record<ReimbursementStatus, string> = {
cancelled: "已取消",
};
function isReimbursementStatus(value: string | null): value is ReimbursementStatus {
return ["draft", "reviewing", "approved", "paid", "rejected", "cancelled"].includes(value ?? "");
}
function formatCurrency(value: string | null | undefined) {
const numberValue = Number(value ?? 0);
if (!Number.isFinite(numberValue)) {
@@ -53,11 +57,13 @@ function formatDate(value: string | null | undefined) {
function ReimbursementCard({
item,
canDeleteDraft,
currentMobileSource,
isDeleting,
onDelete,
}: {
item: ReimbursementListItem;
canDeleteDraft: boolean;
currentMobileSource: string;
isDeleting: boolean;
onDelete: (item: ReimbursementListItem) => void;
}) {
@@ -89,7 +95,9 @@ function ReimbursementCard({
<div className={canDeleteDraft ? "mobile-two-action-grid mt-4" : "mt-4"}>
<Button asChild className="w-full" type="button">
<Link to={`/mobile/reimbursements/${item.id}`}>{actionLabel}</Link>
<Link to={`/mobile/reimbursements/${item.id}`} state={{ from: currentMobileSource }}>
{actionLabel}
</Link>
</Button>
{canDeleteDraft ? (
<Button
@@ -110,12 +118,19 @@ function ReimbursementCard({
export function MobileReimbursementsPage() {
const navigate = useNavigate();
const location = useLocation();
const [searchParams, setSearchParams] = useSearchParams();
const queryClient = useQueryClient();
const { permissions } = useAuth();
const [status, setStatus] = useState<ReimbursementStatus>("draft");
const [status, setStatus] = useState<ReimbursementStatus>(() => {
const queryStatus = searchParams.get("status");
return isReimbursementStatus(queryStatus) ? queryStatus : "draft";
});
const [deleteTarget, setDeleteTarget] = useState<ReimbursementListItem | null>(null);
const createIntentHandledRef = useRef(false);
const canCreateExpense = hasPermission(permissions, "expense.create");
const canDeleteExpense = hasPermission(permissions, "expense.delete_own");
const currentMobileSource = `${location.pathname}${location.search}`;
const listQuery = useQuery({
queryKey: ["mobile", "reimbursements", status],
@@ -127,13 +142,32 @@ export function MobileReimbursementsPage() {
onSuccess: async (response) => {
await queryClient.invalidateQueries({ queryKey: ["mobile", "reimbursements"] });
await queryClient.invalidateQueries({ queryKey: ["reimbursements"] });
navigate(`/mobile/reimbursements/${response.id}`);
navigate(`/mobile/reimbursements/${response.id}`, { state: { from: currentMobileSource } });
},
onError: (error) => {
toast.error(resolveErrorMessage(error));
},
});
useEffect(() => {
const queryStatus = searchParams.get("status");
if (isReimbursementStatus(queryStatus)) {
setStatus(queryStatus);
}
}, [searchParams]);
useEffect(() => {
if (searchParams.get("intent") !== "create" || !canCreateExpense || createIntentHandledRef.current) {
return;
}
createIntentHandledRef.current = true;
const nextParams = new URLSearchParams(searchParams);
nextParams.delete("intent");
setSearchParams(nextParams, { replace: true });
createMutation.mutate();
}, [canCreateExpense, createMutation, searchParams, setSearchParams]);
const deleteMutation = useMutation({
mutationFn: (reimbursementId: string) => deleteReimbursement(reimbursementId),
onSuccess: async () => {
@@ -211,6 +245,7 @@ export function MobileReimbursementsPage() {
{items.map((item) => (
<ReimbursementCard
canDeleteDraft={canDeleteExpense && item.status === "draft"}
currentMobileSource={currentMobileSource}
isDeleting={deleteMutation.isPending}
item={item}
key={item.id}