完善客服工单详情空状态与关联展示
This commit is contained in:
47
docs/操作日志_客服工单详情空状态与关联展示修复_20260817.md
Normal file
47
docs/操作日志_客服工单详情空状态与关联展示修复_20260817.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# 客服工单详情空状态与关联展示修复操作日志
|
||||
|
||||
操作时间:2026-08-17
|
||||
操作类型:修改
|
||||
影响模块:平台总后台 / 客服管理 / 客服工单详情
|
||||
|
||||
## 操作前状态
|
||||
|
||||
- 待受理工单的配送点、处理人员、服务气站、开始时间、完成时间和处理结果统一显示 `-`,无法区分未分派、未开始和未完成等业务阶段。
|
||||
- 配送点、气站和工作人员使用技术标识字段名,存在值时不能优先展示业务名称。
|
||||
- 问题描述、请求流水号和最近操作记录缺失时缺少明确说明。
|
||||
|
||||
## 具体操作
|
||||
|
||||
- 配送点、处理人员和服务气站改为只在详情使用的关系字段,优先展示关联名称并保留唯一标识复制。
|
||||
- 处理人员关系显式允许安装、配送和运维三类工单执行人员,满足工作人员关系加载器的安全过滤要求。
|
||||
- 为未分派、未关联、未预约、未开始、未完成及未提交处理结果设置独立中文空状态。
|
||||
- 增加 `listHidden` 字段能力,详情辅助字段不扩张原有客服工单列表列数。
|
||||
- 新增详情字段从客服工单新建页面隐藏,编辑协议仍只提交原有四个允许字段。
|
||||
- 增加静态展示契约,防止空状态重新退化为裸占位符。
|
||||
|
||||
## 操作后状态
|
||||
|
||||
- 待受理工单详情能准确表达当前处理阶段。
|
||||
- 原列表结构、新建字段、编辑字段及后端接口保持不变。
|
||||
|
||||
## 代码变更
|
||||
|
||||
- `frontend/platform_admin/src/api/resources.ts`:补充客服工单详情关系字段及业务空状态。
|
||||
- `frontend/platform_admin/src/views/shared/resource-list-field-display.ts`:支持详情专用字段从列表隐藏。
|
||||
- `frontend/platform_admin/src/api/resource-page-rules.ts`:隐藏新建页面中的流程只读字段。
|
||||
- `frontend/platform_admin/scripts/check-resource-display-contracts.mjs`:增加客服工单详情空状态契约。
|
||||
- `docs/项目文档_平台资源中文展示统一_v1.0.md`:增加 v1.19 变更记录。
|
||||
|
||||
## 验证结果
|
||||
|
||||
- 资源展示契约检查通过:`pnpm resource-display-contracts:check`。
|
||||
- 后端接口契约检查通过:`pnpm contract:check`。
|
||||
- Biome 代码检查通过:`pnpm exec biome lint src/api/resources.ts src/api/resource-page-rules.ts src/views/shared/resource-list-field-display.ts scripts/check-resource-display-contracts.mjs`。
|
||||
- TypeScript 类型检查通过:`pnpm type:check`。
|
||||
- 前端生产构建通过:`pnpm build`。
|
||||
- 本地浏览器已确认应用可访问且无启动错误;独立测试浏览器未复用用户外部浏览器登录态,因此详情真实数据渲染仍需在已登录页面刷新确认。
|
||||
|
||||
## 风险评估
|
||||
|
||||
- 不修改数据库、接口字段或工单状态流转,仅调整前端展示元数据。
|
||||
- 关系失效时仍保留唯一标识复制能力,便于排障。
|
||||
@@ -81,3 +81,4 @@ frontend/platform_admin/
|
||||
- v1.16:财务对账渠道统一显示余额支付、支付宝、微信支付;账单日期按 `YYYY-MM-DD` 展示,不再回显时间和时区。
|
||||
- v1.17:内容类型统一显示公告、协议,发布状态统一显示草稿、已发布;新建和编辑改用中文下拉并保留未知值提示。
|
||||
- v1.18:客服工单用户统一展示可读名称并保留标识复制;分类与优先级统一显示中文,新建和编辑改用中文下拉并保留未知值提示。
|
||||
- v1.19:客服工单详情的气站、配送点和处理人员统一展示关系名称;未分派、未开始、未完成及未提交结果等空值改用明确业务状态,不再显示裸 `-`。
|
||||
|
||||
@@ -23,6 +23,7 @@ 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 listFieldDisplay = read('src/views/shared/resource-list-field-display.ts');
|
||||
const treePage = read('src/views/shared/TreePage.vue');
|
||||
const identityText = read('src/components/IdentityText.vue');
|
||||
|
||||
@@ -363,6 +364,13 @@ for (const ticketContract of [
|
||||
"unknownValueLabel: '未知工单分类'",
|
||||
"{ label: '普通', value: 'normal' }",
|
||||
"unknownValueLabel: '未知优先级'",
|
||||
"emptyText: '尚未分配配送点'",
|
||||
"emptyText: '尚未分配处理人员'",
|
||||
"staffRelation: { roles: ['installer', 'delivery', 'operations'] }",
|
||||
"emptyText: '尚未关联服务气站'",
|
||||
"emptyText: '尚未开始处理'",
|
||||
"emptyText: '尚未完成'",
|
||||
"emptyText: '尚未提交处理结果'",
|
||||
]) {
|
||||
assertIncludes(
|
||||
resources,
|
||||
@@ -370,6 +378,11 @@ for (const ticketContract of [
|
||||
`客服工单缺少用户关系或中文枚举契约:${ticketContract}`,
|
||||
);
|
||||
}
|
||||
assertIncludes(
|
||||
listFieldDisplay,
|
||||
'!field.listHidden',
|
||||
'仅用于详情的辅助字段不得扩张标准资源列表',
|
||||
);
|
||||
assertIncludes(
|
||||
detailPage,
|
||||
'!hasResourceFieldLabel(props.definition, actualKey)',
|
||||
|
||||
@@ -157,6 +157,19 @@ const pageRules: Record<string, ResourcePageRule> = {
|
||||
editKeys: ['content_type', 'title', 'body', 'version_no', 'publish_status'],
|
||||
},
|
||||
cs_ticket: {
|
||||
createHiddenKeys: [
|
||||
'delivery_basic_identity',
|
||||
'staff_account_identity',
|
||||
'gas_basic_identity',
|
||||
'operator_identity',
|
||||
'description',
|
||||
'request_no',
|
||||
'address',
|
||||
'appointment_at',
|
||||
'started_at',
|
||||
'completed_at',
|
||||
'result',
|
||||
],
|
||||
editKeys: ['user_account_identity', 'ticket_no', 'category', 'priority'],
|
||||
},
|
||||
platform_account: {
|
||||
|
||||
@@ -86,6 +86,8 @@ export type ResourceField = {
|
||||
listDetailLink?: boolean;
|
||||
/** 列表以可读展示字段为主文案,同时保留原关系唯一标识复制入口。 */
|
||||
listDisplayIdentityCopy?: boolean;
|
||||
/** 仅用于详情或表单的辅助字段,不进入标准资源列表。 */
|
||||
listHidden?: boolean;
|
||||
options?: Array<{ label: string; value: string | number }>;
|
||||
defaultValue?: string | number | boolean;
|
||||
readonlyOnCreate?: boolean;
|
||||
@@ -186,6 +188,7 @@ const fieldLabels: Record<string, string> = {
|
||||
repair_type: '检修类型',
|
||||
started_at: '开始时间',
|
||||
completed_at: '完成时间',
|
||||
appointment_at: '预约时间',
|
||||
result: '检修结果',
|
||||
target_product_status: '目标产品状态',
|
||||
content: '内容',
|
||||
@@ -922,6 +925,27 @@ export const resources: ResourceUiDefinition[] = [
|
||||
{ label: '高', value: 'high' },
|
||||
{ label: '紧急', value: 'urgent' },
|
||||
], unknownValueLabel: '未知优先级' }),
|
||||
relation('delivery_basic_identity', '/delivery_basic', false, {
|
||||
label: '配送点', listHidden: true, displayRelationLabel: true,
|
||||
showIdentityCopy: true, emptyText: '尚未分配配送点',
|
||||
}),
|
||||
relation('staff_account_identity', '/staff_account', false, {
|
||||
label: '处理人员', listHidden: true, displayRelationLabel: true,
|
||||
showIdentityCopy: true, emptyText: '尚未分配处理人员',
|
||||
staffRelation: { roles: ['installer', 'delivery', 'operations'] },
|
||||
}),
|
||||
relation('gas_basic_identity', '/gas_basic', false, {
|
||||
label: '服务气站', listHidden: true, displayRelationLabel: true,
|
||||
showIdentityCopy: true, emptyText: '尚未关联服务气站',
|
||||
}),
|
||||
f('operator_identity', { label: '最近操作人标识', listHidden: true, listCopyable: true, emptyText: '暂无操作记录' }),
|
||||
f('description', { label: '问题描述', type: 'textarea', listHidden: true, emptyText: '未填写问题描述' }),
|
||||
f('request_no', { label: '请求流水号', listHidden: true, listCopyable: true, emptyText: '未记录请求流水号' }),
|
||||
f('address', { label: '上门地址', type: 'textarea', listHidden: true, emptyText: '未填写上门地址' }),
|
||||
f('appointment_at', { label: '预约时间', type: 'datetime', listHidden: true, emptyText: '未预约' }),
|
||||
f('started_at', { label: '开始时间', type: 'datetime', listHidden: true, emptyText: '尚未开始处理' }),
|
||||
f('completed_at', { label: '完成时间', type: 'datetime', listHidden: true, emptyText: '尚未完成' }),
|
||||
f('result', { label: '处理结果', type: 'textarea', listHidden: true, emptyText: '尚未提交处理结果' }),
|
||||
]),
|
||||
define('platform_account', '平台账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('display_name'), f('avatar'), f('platform_role_code', { required: true, unknownValueLabel: '未知角色' }), f('phone')]),
|
||||
define('platform_role', '平台角色', 'writable', [f('role_code', { required: true }), f('name', { required: true }), f('location_scope', { required: true, type: 'select', options: resourceSearchEnumOptions('platform_role', 'location_scope') })], 'list', [
|
||||
|
||||
@@ -77,7 +77,12 @@ export function resourceListDisplayFields(
|
||||
...definition.fields.filter((field) => !priority.includes(field.key)),
|
||||
];
|
||||
return ordered
|
||||
.filter((field) => field.key !== 'identity' && field.type !== 'password')
|
||||
.filter(
|
||||
(field) =>
|
||||
field.key !== 'identity' &&
|
||||
field.type !== 'password' &&
|
||||
!field.listHidden,
|
||||
)
|
||||
.filter(
|
||||
(field) =>
|
||||
!(
|
||||
|
||||
Reference in New Issue
Block a user