R103/A1-A6 收口跨页面设计一致性
这个提交包含在:
@@ -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>
|
||||
|
||||
在新工单中引用
屏蔽一个用户