diff --git a/docs/操作日志_商品分类树名称显示修复_20260815.md b/docs/操作日志_商品分类树名称显示修复_20260815.md new file mode 100644 index 0000000..95155d1 --- /dev/null +++ b/docs/操作日志_商品分类树名称显示修复_20260815.md @@ -0,0 +1,45 @@ +# 商品分类树名称显示修复操作日志 + +操作时间:2026-08-15 + +操作类型:修改、扩展 + +影响模块:平台总后台商品分类与平台菜单共享树页面 + +## 操作前状态 + +树组件已将接口 `name` 字段配置为标题来源,但自定义标题插槽错误读取原始节点中不存在的 `title` 字段,导致中文业务名称为空,页面只剩系统唯一标识的末尾 12 位和操作按钮。 + +## 具体操作 + +1. 树节点主标题改为读取接口原始节点的 `name` 字段。 +2. 保留现有系统唯一标识复制、父子层级、编辑、启停和归档行为。 +3. 在平台资源中文展示契约检查中增加树节点名称断言,并禁止回退到错误的 `node.title`。 +4. 按确认方案将共享树页升级为树形表格,增加名称、排序、状态、系统标识和操作列。 +5. 唯一标识由 12 位缩略正文降级为“复制标识”入口,完整值仍可通过提示和剪贴板获取。 +6. 浏览器回归发现 Arco 声明式表格列缺少 `columns` 插槽导致页面空白;补齐插槽并增加静态回归断言。 + +## 操作后状态 + +商品分类和平台菜单均以中文业务名称作为树节点主标题,并通过表格列明确表达排序和状态;系统唯一标识作为次要复制入口展示。商品分类保留编辑、启停和归档操作,平台菜单继续保持只读。 + +## 代码变更 + +- `frontend/platform_admin/src/views/shared/TreePage.vue`:修复树节点主标题字段并补充中文说明。 +- `frontend/platform_admin/src/components/IdentityText.vue`:扩展可选复制入口文案,默认行为保持兼容。 +- `frontend/platform_admin/scripts/check-resource-display-contracts.mjs`:增加树节点中文名称回归检查。 +- `docs/项目文档_平台资源中文展示统一_v1.0.md`:补充 v1.1 变更记录。 + +## 验证结果 + +- `pnpm run resource-display-contracts:check`:通过。 +- `pnpm run type:check`:通过。 +- `pnpm run build`:通过,树形表格改造后 Vite 成功转换 2621 个模块并生成生产构建。 +- 变更文件定向 Biome 检查:退出码为 0;现有 Vue 模板变量识别仍产生非阻断警告,本次未扩大为全仓格式化。 +- 本地浏览器回归:修复前表格容器存在但表头数为 0;补齐 `columns` 插槽后表头数为 5,可正常显示空状态,控制台无错误。 + +## 风险评估 + +- 修改仅影响树节点主标题文本,不改变接口、数据库、树层级、权限或业务动作。 +- 商品分类和平台菜单接口均提供 `name` 字段,兼容现有两类树形资源。 +- 静态回归检查可防止后续再次误用标题插槽参数。 diff --git a/docs/项目文档_平台资源中文展示统一_v1.0.md b/docs/项目文档_平台资源中文展示统一_v1.0.md index b63c0d1..0b570be 100644 --- a/docs/项目文档_平台资源中文展示统一_v1.0.md +++ b/docs/项目文档_平台资源中文展示统一_v1.0.md @@ -61,3 +61,6 @@ frontend/platform_admin/ - v1.0:建立 48 类资源中文展示契约,补齐原 19 类空字段只读资源。 - v1.0:统一业务列优先、关系名称跳转、UUID 次要复制和响应式表格。 - v1.0:建立固定聚合子表和完整 JSON 查看规则。 +- v1.1:修复树标题插槽误读 `title` 导致中文名称为空的问题,并增加树节点名称回归检查。 +- v1.2:树形资源统一改为树形表格,明确展示名称、排序、状态、系统标识和操作,唯一标识降级为复制入口。 +- v1.2.1:补齐 Arco 表格 `columns` 插槽,修复树形表格容器存在但列和数据不渲染的问题。 diff --git a/frontend/platform_admin/scripts/check-resource-display-contracts.mjs b/frontend/platform_admin/scripts/check-resource-display-contracts.mjs index e7a7578..dba21ea 100644 --- a/frontend/platform_admin/scripts/check-resource-display-contracts.mjs +++ b/frontend/platform_admin/scripts/check-resource-display-contracts.mjs @@ -1,6 +1,6 @@ /** * 功能描述:校验平台48类资源均具备中文展示字段,并防止列表、详情退回数据库ID或动态英文列。 - * 版本:v1.0.0 + * 版本:v1.2.1 */ import fs from 'node:fs'; import path from 'node:path'; @@ -23,6 +23,8 @@ const display = read('src/api/resource-display.ts'); const detailContract = read('src/api/resource-detail-contract.ts'); const detailPage = read('src/views/resource/ResourceDetailContent.vue'); const listPage = read('src/views/shared/CrudListPage.vue'); +const treePage = read('src/views/shared/TreePage.vue'); +const identityText = read('src/components/IdentityText.vue'); const resourceNames = [...resources.matchAll(/\bdefine\('([^']+)'/g)].map( (match) => match[1], @@ -81,6 +83,37 @@ if (/title="ID"|data-index="id"/.test(listPage)) { throw new Error('标准资源列表不得把数据库ID作为业务列展示'); } assertIncludes(listPage, 'title="系统唯一标识"', '列表必须中文标注系统唯一标识'); +assertIncludes( + treePage, + '', + 'Arco 树形表格列必须放入 columns 插槽,避免运行时渲染为空白', +); +for (const columnTitle of ['分类名称', '排序', '状态', '系统标识', '操作']) { + assertIncludes(treePage, columnTitle, `树形表格缺少必要列:${columnTitle}`); +} +assertIncludes( + treePage, + '{{ record.name }}', + '树形表格必须以接口返回的中文业务名称作为主标题', +); +assertIncludes( + treePage, + 'display-text="复制标识"', + '树形表格不得把截断唯一标识作为高优先级正文', +); +assertIncludes( + identityText, + 'displayText?: string', + '唯一标识组件必须保留可选的低优先级复制文案能力', +); +if (treePage.includes('node.title')) { + throw new Error('树节点不得读取标题插槽中不存在的 title 字段'); +} assertIncludes( listPage, 'class="resource-table-shell"', diff --git a/frontend/platform_admin/src/components/IdentityText.vue b/frontend/platform_admin/src/components/IdentityText.vue index cb18b38..3720729 100644 --- a/frontend/platform_admin/src/components/IdentityText.vue +++ b/frontend/platform_admin/src/components/IdentityText.vue @@ -1,8 +1,8 @@ - + - {{ shortValue }} + {{ displayText ?? shortValue }} @@ -11,7 +11,7 @@ import { Message } from '@arco-design/web-vue'; import { computed } from 'vue'; -const props = defineProps<{ value: string }>(); +const props = defineProps<{ value: string; displayText?: string }>(); const shortValue = computed(() => props.value.slice(-12)); async function copyIdentity() { diff --git a/frontend/platform_admin/src/views/shared/TreePage.vue b/frontend/platform_admin/src/views/shared/TreePage.vue index 9207e89..47a4ad9 100644 --- a/frontend/platform_admin/src/views/shared/TreePage.vue +++ b/frontend/platform_admin/src/views/shared/TreePage.vue @@ -1,3 +1,4 @@ + @@ -6,20 +7,54 @@ 新增 - - - - {{ node.title }} - - {{ node.group_code || node.path }} - - - 编辑 - {{ node.status === 1 ? '停用' : '启用' }} - 归档 - + + + + + + {{ record.name }} + + {{ record.group_code || record.path }} + + + + + + {{ record.sort_no ?? '-' }} + + + + + {{ recordStatusLabel(Number(record.status)) }} + + + + + + + + + + + + 编辑 + + {{ record.status === 1 ? '停用' : '启用' }} + + 归档 + + + - + @@ -41,6 +76,7 @@ import { Message, Modal } from '@arco-design/web-vue'; import { computed, onMounted, reactive, ref } from 'vue'; import { resourceApi } from '@/api/resource'; +import { recordStatusLabel } from '@/api/resource-display'; import { buildResourcePayload, isMissingField } from '@/api/resource-form'; import type { ResourceUiDefinition } from '@/api/resources'; import { useUserStore } from '@/store'; @@ -71,6 +107,9 @@ const canChangeStatus = computed( const canArchive = computed( () => props.definition.canArchive && rootAllowed.value, ); +const nameColumnTitle = computed(() => + props.definition.name === 'ec_category' ? '分类名称' : '菜单名称', +); const tree = computed(() => { const byIdentity = new Map(); const roots: Node[] = []; @@ -94,6 +133,13 @@ const tree = computed(() => { return roots; }); +/** 返回树形表格状态标签颜色,名称仍由统一状态字典提供。 */ +function statusColor(status: number) { + if (status === 1) return 'green'; + if (status === 2) return 'orange'; + return 'gray'; +} + function reset(data?: Node) { for (const field of props.definition.fields) { const value = data?.[field.key]; @@ -192,7 +238,23 @@ onMounted(load);