feat(08-05-interaction-pattern-unification): 统一前端交互与反馈模式
这个提交包含在:
@@ -1,9 +1,8 @@
|
||||
import { resolveErrorMessage as resolveUserErrorMessage } from "../lib/resolveErrorMessage";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ArrowLeft, ChevronRight } from "lucide-react";
|
||||
import { isApiError } from "../api/types";
|
||||
import { mapApiErrorMessage } from "../api/errorMessages";
|
||||
import { fetchExpenseTypes } from "../api/expenseTypes";
|
||||
import type { ExpenseStandardCheck, PrecheckResponse } from "../api/feeControl";
|
||||
import { fetchProjects } from "../api/projects";
|
||||
@@ -100,15 +99,7 @@ type ItemEditorState = { mode: "create" } | { mode: "edit"; itemId: string } | n
|
||||
type SelectedInvoiceMap = Record<string, string>;
|
||||
|
||||
function buildActionErrorMessage(error: unknown, fallbackMessage: string) {
|
||||
if (isApiError(error)) {
|
||||
return `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
return fallbackMessage;
|
||||
return resolveUserErrorMessage(error, fallbackMessage);
|
||||
}
|
||||
|
||||
export function ReimbursementDetailPage() {
|
||||
@@ -247,21 +238,9 @@ export function ReimbursementDetailPage() {
|
||||
toast.success(actionMessages[params.action] ?? "操作成功");
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isApiError(error)) {
|
||||
const msg = `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`;
|
||||
setActionError(msg);
|
||||
toast.error(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
setActionError(error.message);
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
setActionError("操作失败,请稍后重试");
|
||||
toast.error("操作失败,请稍后重试");
|
||||
const message = buildActionErrorMessage(error, "操作失败,请稍后重试");
|
||||
setActionError(message);
|
||||
toast.error(message);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -315,21 +294,9 @@ export function ReimbursementDetailPage() {
|
||||
toast.success("明细已保存");
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isApiError(error)) {
|
||||
const msg = `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`;
|
||||
setItemError(msg);
|
||||
toast.error(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
setItemError(error.message);
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
setItemError("保存明细失败,请稍后重试");
|
||||
toast.error("保存明细失败,请稍后重试");
|
||||
const message = buildActionErrorMessage(error, "保存明细失败,请稍后重试");
|
||||
setItemError(message);
|
||||
toast.error(message);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -351,21 +318,9 @@ export function ReimbursementDetailPage() {
|
||||
toast.success("明细已删除");
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isApiError(error)) {
|
||||
const msg = `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`;
|
||||
setItemError(msg);
|
||||
toast.error(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
setItemError(error.message);
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
setItemError("删除明细失败,请稍后重试");
|
||||
toast.error("删除明细失败,请稍后重试");
|
||||
const message = buildActionErrorMessage(error, "删除明细失败,请稍后重试");
|
||||
setItemError(message);
|
||||
toast.error(message);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -401,11 +356,7 @@ export function ReimbursementDetailPage() {
|
||||
toast.success("已更新关联项目");
|
||||
},
|
||||
onError: (error) => {
|
||||
const message = isApiError(error)
|
||||
? `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: "关联项目失败";
|
||||
const message = buildActionErrorMessage(error, "关联项目失败");
|
||||
toast.error(message);
|
||||
},
|
||||
});
|
||||
@@ -756,13 +707,7 @@ export function ReimbursementDetailPage() {
|
||||
}
|
||||
|
||||
if (detailQuery.isError || !detailQuery.data) {
|
||||
const message = isApiError(detailQuery.error)
|
||||
? `${detailQuery.error.code}: ${mapApiErrorMessage(
|
||||
detailQuery.error.code,
|
||||
detailQuery.error.details,
|
||||
detailQuery.error.message,
|
||||
)}`
|
||||
: "加载报销详情失败";
|
||||
const message = resolveUserErrorMessage(detailQuery.error, "加载报销详情失败");
|
||||
|
||||
return (
|
||||
<div className="card-std space-y-3 p-5">
|
||||
@@ -893,21 +838,9 @@ export function ReimbursementDetailPage() {
|
||||
|
||||
mutation.mutate({ action: "submit" });
|
||||
} catch (error) {
|
||||
if (isApiError(error)) {
|
||||
const message = `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`;
|
||||
setActionError(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
setActionError(error.message);
|
||||
toast.error(error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
setActionError("提交前校验失败,请稍后重试");
|
||||
toast.error("提交前校验失败,请稍后重试");
|
||||
const message = buildActionErrorMessage(error, "提交前校验失败,请稍后重试");
|
||||
setActionError(message);
|
||||
toast.error(message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户