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

这个提交包含在:
2026-08-03 12:43:00 +08:00
父节点 f4ddc18f12
当前提交 4b0184c64b
共修改 36 个文件,包含 1249 行新增和 178 行删除
+46 -14
查看文件
@@ -30,6 +30,7 @@ import { Input } from "../components/ui/input";
import { Label } from "../components/ui/label";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/ui/tabs";
import { resolveSingleCompanyState } from "../features/company/singleCompany";
const DOCUMENT_ENTITY_TYPE_OPTIONS: Array<{ value: DocumentCategoryEntityType; label: string }> = [
{ value: "reimbursement", label: "报销" },
@@ -132,6 +133,12 @@ export function MasterDataPage() {
}),
});
const activeCompaniesQuery = useQuery({
queryKey: ["master-data", "legal-entities", "active-company-state"],
enabled: canView,
queryFn: () => fetchLegalEntities({ enabled: true }),
});
const costCentersQuery = useQuery({
queryKey: [
"master-data",
@@ -167,6 +174,8 @@ export function MasterDataPage() {
});
const legalEntities = legalEntitiesQuery.data?.items ?? [];
const activeCompanies = activeCompaniesQuery.data?.items ?? [];
const companyState = resolveSingleCompanyState(activeCompanies);
const costCenters = costCentersQuery.data?.items ?? [];
const documentCategories = documentCategoriesQuery.data?.items ?? [];
const masterDataStats = {
@@ -211,7 +220,7 @@ export function MasterDataPage() {
setLegalEntityCurrency("CNY");
setLegalEntityTaxNo("");
setError("");
toast.success("法体已创建");
toast.success("公司信息已创建");
},
onError: (mutationError) => {
const message = resolveError(mutationError);
@@ -312,7 +321,7 @@ export function MasterDataPage() {
<ListWorkspace>
<WorkspaceHeader
title="主数据"
subtitle="集中维护法体、成本中心、单据分类、日历与供应商,为预算、采购、请假与资产扩展提供统一骨架。"
subtitle="集中维护公司信息、成本中心、单据分类、日历与供应商,为预算、采购、请假与资产扩展提供统一基础资料。"
actions={
<Button variant="secondary" type="button" onClick={() => setIncludeArchived((value) => !value)}>
{includeArchived ? "隐藏归档" : "显示归档"}
@@ -325,7 +334,7 @@ export function MasterDataPage() {
) : null}
<div className="finance-status-strip" data-testid="master-data-summary">
<div className="finance-metric-card"><div className="finance-metric-card__label">法体</div><div className="finance-metric-card__value">{masterDataStats.legalEntities}</div></div>
<div className="finance-metric-card"><div className="finance-metric-card__label">公司信息</div><div className="finance-metric-card__value">{masterDataStats.legalEntities}</div></div>
<div className="finance-metric-card"><div className="finance-metric-card__label">成本中心</div><div className="finance-metric-card__value">{masterDataStats.costCenters}</div></div>
<div className="finance-metric-card"><div className="finance-metric-card__label">单据分类</div><div className="finance-metric-card__value">{masterDataStats.documentCategories}</div></div>
<div className="finance-metric-card"><div className="finance-metric-card__label">归档项</div><div className="finance-metric-card__value">{masterDataStats.archived}</div></div>
@@ -334,7 +343,7 @@ export function MasterDataPage() {
<Tabs value={tab} onValueChange={(value) => setTab(value as typeof tab)}>
<TabsList className="ops-command-bar__tabs !w-full !justify-start">
<TabsTrigger value="legal-entities">法体</TabsTrigger>
<TabsTrigger value="legal-entities">公司信息</TabsTrigger>
<TabsTrigger value="cost-centers">成本中心</TabsTrigger>
<TabsTrigger value="document-categories">单据分类</TabsTrigger>
<TabsTrigger value="holiday-calendars">日历</TabsTrigger>
@@ -344,7 +353,7 @@ export function MasterDataPage() {
<TabsContent value="legal-entities" className="space-y-4">
<FilterBar className="ops-command-bar !p-3 md:!p-3.5">
<div className="flex flex-wrap items-center justify-between gap-3">
<h3 className="text-base font-semibold text-zinc-900">法体筛选</h3>
<h3 className="text-base font-semibold text-zinc-900">公司信息筛选</h3>
<select
className="select-std max-w-xs"
value={legalEntityEnabledFilter}
@@ -357,9 +366,18 @@ export function MasterDataPage() {
</div>
</FilterBar>
{canManage ? (
{activeCompaniesQuery.isError ? (
<div className="admin-command-panel border-red-200 bg-red-50/70 text-sm text-red-600">
公司信息读取失败,请刷新后重试。
</div>
) : null}
{canManage && !activeCompaniesQuery.isLoading && !activeCompaniesQuery.isError && companyState.kind === "empty" ? (
<div className="admin-command-panel space-y-4">
<h3 className="text-base font-semibold text-zinc-900">新建法体</h3>
<div>
<h3 className="text-base font-semibold text-zinc-900">完善公司信息</h3>
<p className="mt-1 text-sm text-zinc-600">系统仅服务当前公司。完成后,企业银行账户等配置会自动关联这家公司。</p>
</div>
<div className="grid gap-3 md:grid-cols-3">
<div className="space-y-1">
<Label htmlFor="legalEntityCode">编码</Label>
@@ -387,15 +405,29 @@ export function MasterDataPage() {
disabled={legalEntityCreateMutation.isPending || !legalEntityCode.trim() || !legalEntityName.trim() || !legalEntityCurrency.trim()}
onClick={() => legalEntityCreateMutation.mutate()}
>
{legalEntityCreateMutation.isPending ? "创建中..." : "创建法体"}
{legalEntityCreateMutation.isPending ? "创建中..." : "保存公司信息"}
</Button>
</div>
</div>
</div>
) : null}
{!activeCompaniesQuery.isLoading && !activeCompaniesQuery.isError && companyState.kind === "single" ? (
<div className="admin-command-panel">
<div className="text-sm font-medium text-zinc-900">当前公司:{companyState.company.name}</div>
<div className="mt-1 text-sm text-zinc-600">系统按单公司运行,不需要选择或切换公司。</div>
</div>
) : null}
{!activeCompaniesQuery.isLoading && !activeCompaniesQuery.isError && companyState.kind === "multiple" ? (
<div className="admin-command-panel border-red-200 bg-red-50/70">
<div className="text-sm font-medium text-red-700">检测到多条启用的公司信息</div>
<div className="mt-1 text-sm text-red-600">系统不会自动选择或合并,请先停用多余记录后再继续关联配置。</div>
</div>
) : null}
<div className="admin-command-panel space-y-4">
<h3 className="text-base font-semibold text-zinc-900">法体列表</h3>
<h3 className="text-base font-semibold text-zinc-900">公司信息</h3>
{legalEntitiesQuery.isLoading ? <TableSkeleton columns={6} rows={4} /> : null}
{!legalEntitiesQuery.isLoading ? (
<PrimaryTable className="ops-table-focus">
@@ -443,7 +475,7 @@ export function MasterDataPage() {
) : (
<TableRow>
<TableCell colSpan={6} className="text-center text-sm text-zinc-500">
暂无法体数据
暂无公司信息
</TableCell>
</TableRow>
)}
@@ -471,7 +503,7 @@ export function MasterDataPage() {
value={costCenterLegalEntityFilter}
onChange={(event) => setCostCenterLegalEntityFilter(event.target.value)}
>
<option value="">全部法体</option>
<option value="">全部公司</option>
{legalEntities
.filter((item) => !item.archivedAt)
.map((item) => (
@@ -488,7 +520,7 @@ export function MasterDataPage() {
<h3 className="text-base font-semibold text-zinc-900">新建成本中心</h3>
<div className="grid gap-3 md:grid-cols-4">
<div className="space-y-1 md:col-span-2">
<Label htmlFor="costCenterLegalEntity">所属法体</Label>
<Label htmlFor="costCenterLegalEntity">所属公司</Label>
<select
id="costCenterLegalEntity"
className="select-std"
@@ -498,7 +530,7 @@ export function MasterDataPage() {
setCostCenterParentId("");
}}
>
<option value="">请选择法体</option>
<option value="">请选择公司</option>
{legalEntities
.filter((item) => !item.archivedAt)
.map((item) => (
@@ -561,7 +593,7 @@ export function MasterDataPage() {
<TableRow>
<TableHead>编码</TableHead>
<TableHead>名称</TableHead>
<TableHead>所属法体</TableHead>
<TableHead>所属公司</TableHead>
<TableHead>上级</TableHead>
<TableHead>负责人</TableHead>
<TableHead>状态</TableHead>