R103/A1-A6 收口跨页面设计一致性
这个提交包含在:
@@ -3,12 +3,19 @@ export const ADMIN_NAV_ORDER = ["/system/workspace", "/people", "/organization",
|
||||
export const BACKUP_NAV_ORDER = ["/attendance", "/workflow/tasks", "/contracts-registry", "/purchase-requests", "/travel-requests", "/budgets", "/payment-batches", "/ap-operations", "/invoice-pool", "/inventory-checks", "/system/cutover", "/system/integrations/erp", "/dashboard", "/settings/profile"];
|
||||
|
||||
const PAGE_TITLES: Array<{ path: string; title: string; group: string }> = [
|
||||
{ path: "/portal", title: "Portal", group: "工作台" },
|
||||
{ path: "/portal", title: "工作台", group: "工作台" },
|
||||
{ path: "/announcements", title: "公告", group: "主导航" },
|
||||
{ path: "/team", title: "团队", group: "主导航" },
|
||||
{ path: "/attendance", title: "考勤", group: "备用模块" },
|
||||
{ path: "/projects", title: "项目", group: "主导航" },
|
||||
{ path: "/contracts-registry", title: "合同台账", group: "备用模块" },
|
||||
{ path: "/purchase-requests", title: "采购申请", group: "备用模块" },
|
||||
{ path: "/travel-requests", title: "差旅申请", group: "备用模块" },
|
||||
{ path: "/budgets", title: "预算管理", group: "备用模块" },
|
||||
{ path: "/payment-batches", title: "付款批次", group: "备用模块" },
|
||||
{ path: "/ap-operations", title: "应付作业", group: "备用模块" },
|
||||
{ path: "/invoice-pool", title: "票据中心", group: "备用模块" },
|
||||
{ path: "/inventory-checks", title: "盘点任务", group: "备用模块" },
|
||||
{ path: "/weekly-reports", title: "周报", group: "主导航" },
|
||||
{ path: "/todos", title: "待办", group: "主导航" },
|
||||
{ path: "/reimbursements/", title: "报销详情", group: "主导航" },
|
||||
@@ -29,6 +36,9 @@ const PAGE_TITLES: Array<{ path: string; title: string; group: string }> = [
|
||||
{ path: "/system/workflow-definitions", title: "流程定义", group: "管理入口" },
|
||||
{ path: "/system/settings", title: "系统设置", group: "管理入口" },
|
||||
{ path: "/system/feature-flags", title: "功能开关", group: "管理入口" },
|
||||
{ path: "/system/cutover", title: "切换控制台", group: "管理入口" },
|
||||
{ path: "/system/integrations/erp", title: "ERP 集成", group: "管理入口" },
|
||||
{ path: "/dashboard", title: "数据看板", group: "备用模块" },
|
||||
{ path: "/audit-logs", title: "审计日志", group: "管理入口" },
|
||||
];
|
||||
|
||||
@@ -41,7 +51,7 @@ export function resolvePageMeta(pathname: string) {
|
||||
}
|
||||
|
||||
if (pathname === "/") {
|
||||
return { title: "Portal", group: "工作台" };
|
||||
return { title: "工作台", group: "工作台" };
|
||||
}
|
||||
|
||||
return { title: pathname, group: "工作台" };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
@@ -232,6 +232,8 @@ export function PurchaseRequestsPage() {
|
||||
const [items, setItems] = useState<DraftItem[]>([]);
|
||||
const [archivePoNo, setArchivePoNo] = useState("");
|
||||
const [archiveContractNo, setArchiveContractNo] = useState("");
|
||||
const [selectedAttachmentName, setSelectedAttachmentName] = useState("");
|
||||
const attachmentInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const suppliersQuery = useQuery({
|
||||
queryKey: ["purchase-requests", "suppliers"],
|
||||
@@ -701,7 +703,7 @@ export function PurchaseRequestsPage() {
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="采购申请"
|
||||
subtitle="支持草稿、附件、提交、撤回与经理单步审批的轻采购流程。"
|
||||
subtitle="草稿、附件、审批。"
|
||||
actions={
|
||||
canCreate ? (
|
||||
<Button
|
||||
@@ -1290,17 +1292,41 @@ export function PurchaseRequestsPage() {
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-base font-semibold text-zinc-900">附件</h4>
|
||||
<input
|
||||
type="file"
|
||||
disabled={!canEditDraft || uploadMutation.isPending}
|
||||
onChange={(event) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
uploadMutation.mutate(file);
|
||||
}
|
||||
event.currentTarget.value = "";
|
||||
}}
|
||||
/>
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<input
|
||||
data-testid="purchase-attachment-file-input"
|
||||
hidden
|
||||
ref={attachmentInputRef}
|
||||
type="file"
|
||||
disabled={!canEditDraft || uploadMutation.isPending}
|
||||
onChange={(event) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
setSelectedAttachmentName(file.name);
|
||||
uploadMutation.mutate(file);
|
||||
}
|
||||
event.currentTarget.value = "";
|
||||
}}
|
||||
/>
|
||||
<span
|
||||
className="hidden max-w-[220px] truncate text-xs text-zinc-500 sm:inline"
|
||||
data-testid="purchase-attachment-selected-name"
|
||||
>
|
||||
{uploadMutation.isPending
|
||||
? `正在上传:${uploadMutation.variables?.name ?? selectedAttachmentName}`
|
||||
: selectedAttachmentName || "未选择文件"}
|
||||
</span>
|
||||
<Button
|
||||
data-testid="purchase-attachment-upload"
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={!canEditDraft || uploadMutation.isPending}
|
||||
onClick={() => attachmentInputRef.current?.click()}
|
||||
>
|
||||
{uploadMutation.isPending ? "上传中" : "上传附件"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3 md:hidden">
|
||||
{detail.attachments.length ? (
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { InvoicePoolListResponse } from "../../api/invoicePool";
|
||||
import type { ReimbursementListResponse } from "../../api/reimbursements";
|
||||
import type { TravelRequestDetail, TravelRequestListResponse } from "../../api/travelRequests";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { resolveReimbursementDisplayCode } from "../../lib/reimbursementDisplay";
|
||||
|
||||
export type InvoiceBindMode = "reimbursement" | "travelOrder";
|
||||
|
||||
@@ -87,7 +88,7 @@ export function InvoiceBindCard({
|
||||
<option value="">选择报销单</option>
|
||||
{(reimbursementQuery.data?.items ?? []).map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.formNumber ?? item.id} ({item.status})
|
||||
{resolveReimbursementDisplayCode(item)} ({item.status})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
在新工单中引用
屏蔽一个用户