feat(management-navigation-single-company-ux): 收敛管理导航与单公司配置体验

这个提交包含在:
2026-08-03 12:43:00 +08:00
父节点 f4ddc18f12
当前提交 4b0184c64b
共修改 36 个文件,包含 1249 行新增和 178 行删除
+63 -11
查看文件
@@ -1,4 +1,5 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
@@ -14,8 +15,9 @@ import {
import { fetchLegalEntities } from "../api/masterData";
import { isApiError } from "../api/types";
import { useAuth } from "../auth/AuthContext";
import { hasPermission } from "../auth/permissions";
import { hasAnyPermission, hasPermission } from "../auth/permissions";
import { PageHeader } from "../components/common/PageHeader";
import { resolveSingleCompanyState } from "../features/company/singleCompany";
import { PageScaffold } from "../components/patterns/PageScaffold";
import { SectionPanel } from "../components/patterns/SectionPanel";
import { Button } from "../components/ui/button";
@@ -33,6 +35,7 @@ export function PaymentAccountsPage() {
const { permissions } = useAuth();
const canManage = hasPermission(permissions, "payment.account.manage");
const canReveal = hasPermission(permissions, "payment.account.read_full");
const canManageCompanyInformation = hasAnyPermission(permissions, ["masterdata.view", "masterdata.manage"]);
const [editing, setEditing] = useState<PaymentAccount | null>(null);
const [legalEntityId, setLegalEntityId] = useState("");
const [bankInstitutionId, setBankInstitutionId] = useState("");
@@ -51,9 +54,19 @@ export function PaymentAccountsPage() {
enabled: canManage,
});
const activeCompanies = legalEntitiesQuery.data?.items ?? [];
const companyState = resolveSingleCompanyState(activeCompanies);
const singleCompany = companyState.kind === "single" ? companyState.company : null;
useEffect(() => {
if (!editing && singleCompany) {
setLegalEntityId(singleCompany.id);
}
}, [editing, singleCompany]);
const resetForm = () => {
setEditing(null);
setLegalEntityId("");
setLegalEntityId(singleCompany?.id ?? "");
setBankInstitutionId("");
setAccountName("");
setAccountNumber("");
@@ -93,17 +106,56 @@ export function PaymentAccountsPage() {
const accounts = accountsQuery.data?.items ?? [];
return (
<PageScaffold>
<PageHeader title="企业银行账户" subtitle="维护法人主体的付款来源账户;完整账号仅在填写原因后临时显示。" />
<PageHeader title="企业银行账户" subtitle="维护当前公司的付款来源账户;完整账号仅在填写原因后临时显示。" />
<div className="mx-auto max-w-6xl space-y-5">
{canManage ? (
{canManage && legalEntitiesQuery.isLoading ? (
<SectionPanel title="企业银行账户">
<div className="text-sm text-zinc-500">正在读取公司信息...</div>
</SectionPanel>
) : null}
{canManage && legalEntitiesQuery.isError ? (
<SectionPanel title="企业银行账户">
<div className="text-sm text-red-600">公司信息读取失败,请刷新后重试。</div>
</SectionPanel>
) : null}
{canManage && !legalEntitiesQuery.isLoading && !legalEntitiesQuery.isError && companyState.kind === "empty" ? (
<SectionPanel title="先完善公司信息">
<div className="space-y-3 text-sm text-zinc-600">
<p>企业银行账户必须关联当前公司。请先完善公司信息,再回来录入付款账户。</p>
{canManageCompanyInformation ? (
<Button asChild type="button" variant="secondary">
<Link to="/system/master-data">完善公司信息</Link>
</Button>
) : (
<p>请联系管理员先完善公司信息。</p>
)}
</div>
</SectionPanel>
) : null}
{canManage && companyState.kind === "multiple" ? (
<SectionPanel title="公司信息需要处理">
<div className="space-y-3 text-sm text-red-600">
<p>检测到多条启用的公司信息,系统不会猜测付款账户应关联哪一条。请先处理公司信息。</p>
{canManageCompanyInformation ? (
<Button asChild type="button" variant="secondary">
<Link to="/system/master-data">查看公司信息</Link>
</Button>
) : null}
</div>
</SectionPanel>
) : null}
{canManage && singleCompany ? (
<SectionPanel title={editing ? "替换企业银行账户" : "新增企业银行账户"}>
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="payment-account-legal-entity">法人主体</Label>
<select id="payment-account-legal-entity" className="select-std w-full" value={legalEntityId} disabled={Boolean(editing)} onChange={(event) => setLegalEntityId(event.target.value)}>
<option value="">请选择</option>
{(legalEntitiesQuery.data?.items ?? []).map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
</select>
<Label>当前公司</Label>
<div className="input-std flex min-h-10 items-center bg-zinc-50 text-sm text-zinc-700" data-testid="payment-account-company">
{editing?.legalEntity.name ?? singleCompany.name}
</div>
</div>
<div className="space-y-2">
<Label htmlFor="payment-account-bank">开户银行</Label>
@@ -158,7 +210,7 @@ export function PaymentAccountsPage() {
</Dialog>
<Dialog open={Boolean(disableTarget)} onOpenChange={(open) => { if (!open) setDisableTarget(null); }}>
<DialogContent className="max-w-md p-5">
<DialogHeader><DialogTitle>停用企业银行账户</DialogTitle><DialogDescription>停用后不能为新付款批次生成制单;历史批次仍保留冻结账户摘要。确认停用 {disableTarget?.legalEntity.name ?? "该法人主体"} 的当前账户吗?</DialogDescription></DialogHeader>
<DialogHeader><DialogTitle>停用企业银行账户</DialogTitle><DialogDescription>停用后不能为新付款批次生成制单;历史批次仍保留冻结账户摘要。确认停用 {disableTarget?.legalEntity.name ?? "当前公司"} 的当前账户吗?</DialogDescription></DialogHeader>
<DialogFooter><Button type="button" variant="secondary" onClick={() => setDisableTarget(null)}>返回</Button><Button type="button" variant="destructive" disabled={disableMutation.isPending} onClick={() => { if (disableTarget) disableMutation.mutate(disableTarget.id); }}>{disableMutation.isPending ? "停用中..." : "确认停用"}</Button></DialogFooter>
</DialogContent>
</Dialog>