diff --git a/client/src/api/errorMessages.ts b/client/src/api/errorMessages.ts index 880480ba..7b509363 100644 --- a/client/src/api/errorMessages.ts +++ b/client/src/api/errorMessages.ts @@ -11,7 +11,7 @@ function readStringList(value: unknown): string[] { } const FEATURE_FLAG_DISABLED_MESSAGES: Record = { - portal_home_v1_disabled: "Portal 首页当前未启用,请联系管理员检查环境开关与功能开关配置。", + portal_home_v1_disabled: "工作台首页当前未启用,请联系管理员检查环境开关与功能开关配置。", announcements_v1_disabled: "公告模块当前未启用,请联系管理员检查环境开关与功能开关配置。", presence_board_v1_disabled: "团队状态模块当前未启用,请联系管理员检查环境开关与功能开关配置。", attendance_v1_disabled: "历史 OA 自建打卡已关闭,请在企业微信打卡。", diff --git a/client/src/components/common/StatusBadge.tsx b/client/src/components/common/StatusBadge.tsx index 63ac7ddb..a2c16bac 100644 --- a/client/src/components/common/StatusBadge.tsx +++ b/client/src/components/common/StatusBadge.tsx @@ -8,6 +8,8 @@ const STATUS_MAP: Record = { pending: { label: "待派发", className: "badge-warning" }, processing: { label: "处理中", className: "badge-warning" }, succeeded: { label: "已成功", className: "badge-success" }, + reviewed: { label: "已复核", className: "badge-primary" }, + executed: { label: "已执行", className: "badge-success" }, failed: { label: "失败", className: "badge-error" }, dead_letter: { label: "死信", className: "badge-error" }, not_triggered: { label: "未触发", className: "badge-neutral" }, diff --git a/client/src/components/navigation/NavLinks.tsx b/client/src/components/navigation/NavLinks.tsx index c0b5926d..88953899 100644 --- a/client/src/components/navigation/NavLinks.tsx +++ b/client/src/components/navigation/NavLinks.tsx @@ -18,7 +18,7 @@ export type NavItem = { }; export const NAV_ITEMS: NavItem[] = [ - { to: "/portal", label: "Portal", icon: "home_app_logo" }, + { to: "/portal", label: "工作台", icon: "home_app_logo" }, { to: "/team", label: "团队", icon: "groups" }, { to: "/leave-requests", label: "请假", icon: "event_available", anyOf: ["leave.view_own", "leave.view_all", "leave.manage"] }, { to: "/attendance", label: "考勤", icon: "schedule" }, @@ -40,7 +40,7 @@ export const NAV_ITEMS: NavItem[] = [ { to: "/purchase-requests", label: "采购申请", icon: "shopping_cart", anyOf: ["procurement.create", "procurement.view_own", "procurement.view_all", "procurement.manage"] }, { to: "/travel-requests", label: "差旅申请", icon: "flight", anyOf: ["travel.view_all", "travel.manage", "travel.payment.manage"] }, { to: "/budgets", label: "预算管理", icon: "account_balance", anyOf: ["budget.view", "budget.manage"] }, - { to: "/payment-batches", label: "支付批次", icon: "payments", anyOf: ["payment.batch.manage", "risk.review.manage"] }, + { to: "/payment-batches", label: "付款批次", icon: "payments", anyOf: ["payment.batch.manage", "risk.review.manage"] }, { to: "/ap-operations", label: "AP 运营", icon: "account_balance_wallet", anyOf: ["payment.batch.manage", "risk.review.manage"] }, { to: "/invoice-pool", label: "票据中心", icon: "receipt_long", anyOf: ["invoice.manage"] }, { to: "/inventory-checks", label: "资产盘点", icon: "inventory", anyOf: ["asset.inventory"] }, diff --git a/client/src/lib/displayFormat.ts b/client/src/lib/displayFormat.ts new file mode 100644 index 00000000..2f485ab8 --- /dev/null +++ b/client/src/lib/displayFormat.ts @@ -0,0 +1,32 @@ +export function formatDisplayDate(value: string | null | undefined) { + if (!value) return "-"; + + const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(value); + if (match) { + const [, year, month, day] = match; + return `${Number(year)}年${Number(month)}月${Number(day)}日`; + } + + const timestamp = new Date(value).getTime(); + if (Number.isNaN(timestamp)) return "-"; + return new Intl.DateTimeFormat("zh-CN", { + year: "numeric", + month: "long", + day: "numeric", + }).format(timestamp); +} + +export function formatDisplayDateTime(value: string | null | undefined) { + if (!value) return "-"; + + const timestamp = new Date(value).getTime(); + if (Number.isNaN(timestamp)) return formatDisplayDate(value); + + return new Intl.DateTimeFormat("zh-CN", { + year: "numeric", + month: "long", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }).format(timestamp); +} diff --git a/client/src/pages/ApOperationsPage.tsx b/client/src/pages/ApOperationsPage.tsx index 5671e9d8..31ad87c1 100644 --- a/client/src/pages/ApOperationsPage.tsx +++ b/client/src/pages/ApOperationsPage.tsx @@ -646,7 +646,7 @@ export function ApOperationsPage() { ) : null} {paymentBatchesQuery.isError ? ( - + ) : null} {riskFlagsQuery.isError ? ( @@ -1596,7 +1596,7 @@ export function ApOperationsPage() { title="确认取消付款批次" description={ cancelTarget - ? `将取消 ${cancelTarget.batchNo},该操作与支付批次页保持一致。` + ? `将取消 ${cancelTarget.batchNo},该操作与付款批次页保持一致。` : undefined } variant="destructive" diff --git a/client/src/pages/ContractsRegistryPage.tsx b/client/src/pages/ContractsRegistryPage.tsx index df323ba8..9dd4b9b0 100644 --- a/client/src/pages/ContractsRegistryPage.tsx +++ b/client/src/pages/ContractsRegistryPage.tsx @@ -28,6 +28,7 @@ import { Input } from "../components/ui/input"; import { Label } from "../components/ui/label"; import { Textarea } from "../components/ui/textarea"; import { downloadBlob } from "../lib/download"; +import { formatDisplayDate } from "../lib/displayFormat"; import { toast } from "sonner"; const CONFIDENTIALITY_OPTIONS: ConfidentialityLevel[] = ["public", "internal", "confidential", "restricted"]; @@ -39,9 +40,9 @@ const FILTER_STATUS_OPTIONS: Array<{ value: ContractRegistryStatus; label: strin { value: "archived", label: "已归档" }, ]; const EDITOR_STATUS_OPTIONS: Array<{ value: ContractRegistryPersistedStatus; label: string }> = [ - { value: "active", label: "active" }, - { value: "draft", label: "draft" }, - { value: "archived", label: "archived" }, + { value: "active", label: "生效中" }, + { value: "draft", label: "草稿" }, + { value: "archived", label: "已归档" }, ]; const AMOUNT_PATTERN = /^\d{1,12}(\.\d{1,2})?$/; const EXPIRING_DAYS = 30; @@ -95,6 +96,14 @@ function contractTypeLabel(value: ContractRegistryType) { return "其他"; } +function contractStatusLabel(value: ContractRegistryStatus | ContractRegistryPersistedStatus) { + if (value === "active") return "生效中"; + if (value === "expired") return "已过期"; + if (value === "draft") return "草稿"; + if (value === "archived") return "已归档"; + return value || "-"; +} + function buildEmptyEditor(projectId = ""): EditorState { return { contractNo: "", @@ -346,7 +355,7 @@ export function ContractsRegistryPage() { {item.contractNo} · {item.counterpartyName}
- {contractTypeLabel(item.contractType)} · 到期日 {item.expiryDate ?? "-"} · 责任人 {item.owner?.name ?? "-"} + {contractTypeLabel(item.contractType)} · 到期日 {formatDisplayDate(item.expiryDate)} · 责任人 {item.owner?.name ?? "-"}
@@ -389,7 +398,7 @@ export function ContractsRegistryPage() { {editingItem?.status === "expired" ? (
- 当前合同因到期日已过,列表状态显示为“已过期”;保存时仍以数据库状态 {editingItem.persistedStatus} 持久化。 + 当前合同因到期日已过,列表状态显示为“已过期”;保存时仍按“{contractStatusLabel(editingItem.persistedStatus)}”留存。
) : null} @@ -705,7 +714,7 @@ export function ContractsRegistryPage() {
{item.contractNo}
- + {confidentialityLabel(item.confidentialityLevel)} @@ -715,14 +724,14 @@ export function ContractsRegistryPage() {
{contractTypeLabel(item.contractType)} {item.amount ? ` · ${item.amount} ${item.currency}` : " · 金额未录入"} - {item.expiryDate ? ` · 到期日 ${item.expiryDate}` : " · 未设置到期日"} + {item.expiryDate ? ` · 到期日 ${formatDisplayDate(item.expiryDate)}` : " · 未设置到期日"}
项目:{item.project ? `${item.project.projectNo} · ${item.project.name}` : "未关联"} · 责任人: {item.owner?.name ?? "-"}
- 签署:{item.signedDate ?? "-"} · 生效:{item.effectiveDate ?? "-"} · 归档:{item.archiveLocation} + 签署:{formatDisplayDate(item.signedDate)} · 生效:{formatDisplayDate(item.effectiveDate)} · 归档:{item.archiveLocation} {item.offlineArchiveNo ? ` · 离线档案号 ${item.offlineArchiveNo}` : ""}
@@ -760,7 +769,7 @@ export function ContractsRegistryPage() { disabled={toggleStatusMutation.isPending} onClick={() => toggleStatusMutation.mutate(item)} > - {item.persistedStatus === "archived" ? "恢复为 active" : "归档"} + {item.persistedStatus === "archived" ? "恢复生效" : "归档"}
diff --git a/client/src/pages/FeatureFlagsPage.tsx b/client/src/pages/FeatureFlagsPage.tsx index 3d2935a2..f97869f4 100644 --- a/client/src/pages/FeatureFlagsPage.tsx +++ b/client/src/pages/FeatureFlagsPage.tsx @@ -50,8 +50,8 @@ function toDisplayName(flagKey: string) { if (flagKey === "invoice_verification") return "发票验真"; if (flagKey === "leave_holiday_calendar") return "请假日历增强"; if (flagKey === "budget_precheck_v1") return "预算预校验"; - if (flagKey === "payment_batch_v1") return "支付批次"; - if (flagKey === "portal_home_v1") return "Portal 首页"; + if (flagKey === "payment_batch_v1") return "付款批次"; + if (flagKey === "portal_home_v1") return "工作台首页"; if (flagKey === "global_search_v1") return "全局搜索"; if (flagKey === "announcements_v1") return "公告中心"; if (flagKey === "workflow_definition_admin_v1") return "流程定义治理"; diff --git a/client/src/pages/PaymentBatchesPage.tsx b/client/src/pages/PaymentBatchesPage.tsx index c63b3388..fe2e9541 100644 --- a/client/src/pages/PaymentBatchesPage.tsx +++ b/client/src/pages/PaymentBatchesPage.tsx @@ -27,7 +27,9 @@ import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Label } from "../components/ui/label"; import { RiskFlagsCard } from "../components/common/RiskFlagsCard"; +import { StatusBadge } from "../components/common/StatusBadge"; import { downloadBlob } from "../lib/download"; +import { formatDisplayDate, formatDisplayDateTime } from "../lib/displayFormat"; import { toast } from "sonner"; const PAYMENT_CHANNEL_OPTIONS: PaymentChannel[] = ["bank", "alipay", "wechat", "cash", "other"]; @@ -47,6 +49,31 @@ function entityTypeLabel(value: string) { return value || "-"; } +function paymentChannelLabel(value: PaymentChannel | string | null | undefined) { + if (value === "bank") return "银行转账"; + if (value === "alipay") return "支付宝"; + if (value === "wechat") return "微信"; + if (value === "cash") return "现金"; + if (value === "other") return "其他"; + return value || "-"; +} + +function paymentItemStatusLabel(value: string) { + if (value === "pending") return "待付款"; + if (value === "paid") return "已支付"; + if (value === "executed") return "已执行"; + if (value === "failed") return "付款失败"; + if (value === "cancelled") return "已取消"; + if (value === "skipped") return "已跳过"; + return value || "-"; +} + +function reviewNextStepLabel(value: PaymentBatch["reviewRequirement"]["nextStep"]) { + if (value === "first_review") return "等待首轮复核"; + if (value === "second_review") return "等待二次复核"; + return "可执行付款标记"; +} + function formatFileSize(value: number) { if (!Number.isFinite(value) || value <= 0) return "-"; if (value < 1024) return `${value} B`; @@ -54,11 +81,6 @@ function formatFileSize(value: number) { return `${(value / 1024 / 1024).toFixed(1)} MB`; } -function formatDateTime(value: string | null | undefined) { - if (!value) return "-"; - return new Date(value).toLocaleString(); -} - function resolveError(error: unknown) { return isApiError(error) ? `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}` @@ -303,7 +325,7 @@ export function PaymentBatchesPage() { return ( - + {error ?
{error}
: null} @@ -337,13 +359,13 @@ export function PaymentBatchesPage() { />
- +