From f1d77db79a343d25c62cefa2d82b19b712b55007 Mon Sep 17 00:00:00 2001 From: wren <“porlong@qq.com”> Date: Wed, 6 May 2026 10:54:42 +0800 Subject: [PATCH] fix: clarify document type grouping hierarchy --- app/routes/document-types._index.tsx | 57 +++++++++++++++++++++-- app/routes/document-types.new.tsx | 46 +++++++++++++++--- app/styles/pages/document-types_index.css | 16 +++++++ 3 files changed, 108 insertions(+), 11 deletions(-) diff --git a/app/routes/document-types._index.tsx b/app/routes/document-types._index.tsx index 66beae6..1fae6ee 100644 --- a/app/routes/document-types._index.tsx +++ b/app/routes/document-types._index.tsx @@ -11,6 +11,7 @@ import { type DocumentType, type EntryModuleOption, } from "~/api/document-types/document-types"; +import { getDocumentSubtypeGroups, type DocumentSubtypeGroup } from "~/api/files/files-upload"; import documentTypesStyles from "~/styles/pages/document-types_index.css?url"; export function links() { @@ -27,6 +28,7 @@ export const meta: MetaFunction = () => { interface LoaderData { types: DocumentType[]; entryModules: EntryModuleOption[]; + subtypeGroupsByTypeId: Record; frontendJWT?: string | null; error?: string; } @@ -41,13 +43,22 @@ export async function loader({ request }: LoaderFunctionArgs) { getEntryModules(frontendJWT), ]); + const types = typesRes.data?.types || []; + const subtypeGroupEntries = await Promise.all( + types.map(async (type) => { + const groupsRes = await getDocumentSubtypeGroups(type.id, frontendJWT, type.entryModuleId || undefined); + return [type.id, "data" in groupsRes && groupsRes.data ? groupsRes.data : []] as const; + }), + ); + return { - types: typesRes.data?.types || [], + types, entryModules: modulesRes.data || [], + subtypeGroupsByTypeId: Object.fromEntries(subtypeGroupEntries), frontendJWT, }; } catch (error) { - return { types: [], entryModules: [], error: "加载失败" }; + return { types: [], entryModules: [], subtypeGroupsByTypeId: {}, error: "加载失败" }; } } @@ -56,6 +67,7 @@ export default function DocumentTypesIndex() { const loaderData = useLoaderData(); const [types, setTypes] = useState(loaderData.types || []); const entryModules = loaderData.entryModules || []; + const subtypeGroupsByTypeId = loaderData.subtypeGroupsByTypeId || {}; useEffect(() => { setTypes(loaderData.types || []); @@ -66,6 +78,17 @@ export default function DocumentTypesIndex() { return entryModules.find((m) => m.id === id)?.name || `#${id}`; }; + const getSubtypeGroups = (typeId: number) => subtypeGroupsByTypeId[typeId] || []; + + const getRootGroupSummary = (typeId: number) => { + const groups = getSubtypeGroups(typeId); + const rootNames = Array.from(new Set(groups.map((group) => group.rootGroupName).filter(Boolean))); + if (rootNames.length > 0) { + return rootNames.join("、"); + } + return "未映射一级分组"; + }; + const handleDelete = async (type: DocumentType) => { const confirmed = window.confirm(`确定要删除文档类型「${type.name}」吗?`); if (!confirmed) return; @@ -103,7 +126,8 @@ export default function DocumentTypesIndex() { 编码 名称 - 入口模块 + 所属大类 + 运行子类型映射 汇总规则集 状态 操作 @@ -113,8 +137,31 @@ export default function DocumentTypesIndex() { {types.map((type) => ( {type.code} - {type.name} - {getEntryModuleName(type.entryModuleId)} + +
+ {type.name} + 文档类型 +
+ + +
+ {getEntryModuleName(type.entryModuleId)} + {getRootGroupSummary(type.id)} +
+ + +
+ {getSubtypeGroups(type.id).length > 0 ? ( + getSubtypeGroups(type.id).map((group) => ( + + {group.displayName || group.name} + + )) + ) : ( + 未映射运行子类型 + )} +
+ {type.ruleSetIds?.length || 0} 个规则集 diff --git a/app/routes/document-types.new.tsx b/app/routes/document-types.new.tsx index cbe846c..930ab48 100644 --- a/app/routes/document-types.new.tsx +++ b/app/routes/document-types.new.tsx @@ -79,6 +79,16 @@ export default function DocumentTypeNew() { const selectedModule = loaderData.entryModules.find((m) => m.id === entryModuleId); const runtimeSubtypeGroups = loaderData.runtimeSubtypeGroups || []; + const runtimeRootGroups = Array.from( + new Map( + runtimeSubtypeGroups + .filter((group) => group.rootGroupId || group.rootGroupName) + .map((group) => [group.rootGroupId || group.rootGroupName || group.id, { + id: group.rootGroupId || null, + name: group.rootGroupName || "未归属一级分组", + }]), + ).values(), + ); const ruleSetsReadonly = isEdit && runtimeSubtypeGroups.length > 0; const selectedRuleSets = loaderData.ruleSets.filter((rs) => selectedRuleSetIds.includes(rs.id)); const selectedUnavailableRuleSets = selectedRuleSets.filter((rs) => !rs.hasUsableVersion); @@ -304,13 +314,24 @@ export default function DocumentTypeNew() {
-
当前路由预览
+
当前归属大类
{selectedModule?.name || "未绑定入口模块,上传入口不会主动露出此类型"}
+
+ +
+ 这里绑定的是文档类型所属大类,不是二级分组 + + 文档类型先归属到入口模块 / 一级大类(例如:合同、卷宗), + 再由评查点分组页把它映射到具体运行子类型(例如:建设工程合同、借款合同、许可-停业办理)。 + +
+
+ {isEdit ? (
@@ -318,7 +339,7 @@ export default function DocumentTypeNew() { 当前运行链路摘要 {runtimeSubtypeGroups.length > 0 - ? `当前文档类型在评查点分组中已挂到 ${runtimeSubtypeGroups.length} 个二级分组;上传时会先按入口模块和子类型命中,再决定实际规则集。` + ? `当前文档类型在评查点分组中已映射到 ${runtimeSubtypeGroups.length} 个运行子类型;上传时会先命中所属大类,再按所选子类型决定实际规则集。` : "当前文档类型还没有在评查点分组中挂到任何二级分组,上传时无法形成完整的新链路。"}
@@ -328,8 +349,18 @@ export default function DocumentTypeNew() {
- 已关联的运行子类型 - 这里只展示实际会命中的二级分组,最终规则仍以分组页绑定为准。 + 运行子类型映射 + 这里只展示实际会命中的二级分组;文档类型归属仍以上面的入口模块 / 一级大类为准。 +
+
+
+ +
+ 一级大类 + + {runtimeRootGroups.map((group) => group.name).join("、")} + {selectedModule?.name ? ` · 所属入口模块:${selectedModule.name}` : ""} +
@@ -337,9 +368,12 @@ export default function DocumentTypeNew() {
{group.displayName || group.name} - {group.rootGroupName || "未归属一级分组"}{group.entryModuleName ? ` · ${group.entryModuleName}` : ""} + + 一级分组:{group.rootGroupName || "未归属一级分组"} + {group.entryModuleName ? ` · 入口模块:${group.entryModuleName}` : ""} +
- {group.code} + 运行子类型 · {group.code}
))}
diff --git a/app/styles/pages/document-types_index.css b/app/styles/pages/document-types_index.css index e534933..0c01e59 100644 --- a/app/styles/pages/document-types_index.css +++ b/app/styles/pages/document-types_index.css @@ -62,6 +62,22 @@ @apply flex flex-wrap gap-1 max-w-md; } +.document-types-page .type-cell { + @apply flex flex-col gap-1; +} + +.document-types-page .type-cell strong { + @apply text-sm font-medium text-gray-800; +} + +.document-types-page .type-cell span { + @apply text-xs text-gray-500; +} + +.document-types-page .subtle-text { + @apply text-xs text-gray-400; +} + .document-types-page .operation-btn { @apply inline-flex items-center text-sm px-2 py-1 rounded hover:bg-gray-100; }