From 3ba05dc147f8aef18a64c03fa8d349b9c76d3d69 Mon Sep 17 00:00:00 2001
From: czl231 <3286836406@qq.com>
Date: Fri, 14 Aug 2026 21:02:02 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=85=8D=E9=80=81=E8=AE=A2?=
=?UTF-8?q?=E5=8D=95=E5=88=97=E8=A1=A8=E5=8F=AF=E8=AF=BB=E4=BF=A1=E6=81=AF?=
=?UTF-8?q?=E6=98=BE=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../logic/platform/gasorder/gasorder.go | 1 -
.../platform/gasorder/order_list_display.go | 190 ++++++++++++++++++
.../gasorder/order_list_display_test.go | 38 ++++
...作日志_配送订单列表可读信息优化_20260814.md | 44 ++++
frontend/platform_admin/src/api/resources.ts | 10 +-
.../src/views/shared/CrudListPage.vue | 7 +
6 files changed, 286 insertions(+), 4 deletions(-)
create mode 100644 backend/api/internal/logic/platform/gasorder/order_list_display.go
create mode 100644 backend/api/internal/logic/platform/gasorder/order_list_display_test.go
create mode 100644 docs/操作日志_配送订单列表可读信息优化_20260814.md
diff --git a/backend/api/internal/logic/platform/gasorder/gasorder.go b/backend/api/internal/logic/platform/gasorder/gasorder.go
index 05062d9..28b9033 100644
--- a/backend/api/internal/logic/platform/gasorder/gasorder.go
+++ b/backend/api/internal/logic/platform/gasorder/gasorder.go
@@ -82,7 +82,6 @@ func ListGasorderContractRevision(ctx *gin.Context) {
func GetGasorderContractRevision(ctx *gin.Context) {
common.GetResource(ctx, &models.GasorderContractRevision{})
}
-func ListGasorderBasic(ctx *gin.Context) { common.ListResource(ctx, &models.GasorderBasic{}) }
func GetGasorderBasic(ctx *gin.Context) { getGasorderBasic(ctx) }
func ListGasorderAssign(ctx *gin.Context) { common.ListResource(ctx, &models.GasorderAssign{}) }
func GetGasorderAssign(ctx *gin.Context) { common.GetResource(ctx, &models.GasorderAssign{}) }
diff --git a/backend/api/internal/logic/platform/gasorder/order_list_display.go b/backend/api/internal/logic/platform/gasorder/order_list_display.go
new file mode 100644
index 0000000..f48c26f
--- /dev/null
+++ b/backend/api/internal/logic/platform/gasorder/order_list_display.go
@@ -0,0 +1,190 @@
+// 功能描述:为平台配送订单列表补充创建方名称、收货地址和合同气瓶摘要。
+// 版本:v1.0.0
+package gasorder
+
+import (
+ "fmt"
+ "strings"
+
+ "git.apinb.com/bsm-sdk/core/infra"
+ "git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
+ "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
+ "git.apinb.com/heqiapp/platforms/backend/api/internal/models"
+ "github.com/gin-gonic/gin"
+)
+
+// gasorderListDisplay 是订单持久化快照与列表专用可读字段组成的响应模型。
+type gasorderListDisplay struct {
+ models.GasorderBasic
+ CreatorDisplayName string `json:"creator_display_name"`
+ ContractProductsSummary string `json:"contract_products_summary"`
+}
+
+// ListGasorderBasic 返回平台订单分页列表,并补充只用于管理端展示的可读摘要。
+func ListGasorderBasic(ctx *gin.Context) {
+ page, size := common.PageSize(ctx)
+ var orders []models.GasorderBasic
+ var total int64
+ query := common.ApplyKeywordFilter(ctx, common.ActiveRecords(impl.DBService.Model(&models.GasorderBasic{})), &models.GasorderBasic{})
+ if err := query.Count(&total).Error; err != nil {
+ infra.Response.Error(ctx, err)
+ return
+ }
+ if err := query.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&orders).Error; err != nil {
+ infra.Response.Error(ctx, err)
+ return
+ }
+ displays, err := buildGasorderListDisplays(orders)
+ if err != nil {
+ infra.Response.Error(ctx, err)
+ return
+ }
+ response, err := common.PublicResourceResponse(displays)
+ if err != nil {
+ infra.Response.Error(ctx, err)
+ return
+ }
+ protected := common.ProtectPreciseLocation(ctx, &models.GasorderBasic{}, response)
+ infra.Response.Success(ctx, gin.H{"total": total, "list": restoreGasorderListAddresses(protected, orders)})
+}
+
+// buildGasorderListDisplays 批量读取创建方与有效订单气瓶,避免列表逐行查询。
+func buildGasorderListDisplays(orders []models.GasorderBasic) ([]gasorderListDisplay, error) {
+ creatorNames, err := loadGasorderCreatorNames(orders)
+ if err != nil {
+ return nil, err
+ }
+ productSummaries, err := loadGasorderProductSummaries(orders)
+ if err != nil {
+ return nil, err
+ }
+ displays := make([]gasorderListDisplay, 0, len(orders))
+ for _, order := range orders {
+ creatorName := strings.TrimSpace(creatorNames[creatorLookupKey(order.CreatorType, order.CreatorID)])
+ if creatorName == "" {
+ creatorName = fmt.Sprintf("记录已失效(%s)", order.CreatorIdentity)
+ }
+ displays = append(displays, gasorderListDisplay{
+ GasorderBasic: order,
+ CreatorDisplayName: creatorName,
+ ContractProductsSummary: productSummaries[order.ID],
+ })
+ }
+ return displays, nil
+}
+
+func creatorLookupKey(creatorType string, creatorID uint64) string {
+ return fmt.Sprintf("%s:%d", creatorType, creatorID)
+}
+
+// loadGasorderCreatorNames 按四类创建方批量加载当前可读名称,归档或已删除记录不参与展示。
+func loadGasorderCreatorNames(orders []models.GasorderBasic) (map[string]string, error) {
+ ids := map[string][]uint64{}
+ for _, order := range orders {
+ ids[order.CreatorType] = append(ids[order.CreatorType], order.CreatorID)
+ }
+ names := map[string]string{}
+ load := func(creatorType string, records any) error {
+ if len(ids[creatorType]) == 0 {
+ return nil
+ }
+ return common.ActiveRecords(impl.DBService).Where("id IN ? AND status <> ?", ids[creatorType], common.StatusArchived).Find(records).Error
+ }
+ var users []models.UserAccount
+ if err := load("user", &users); err != nil {
+ return nil, err
+ }
+ for _, item := range users {
+ names[creatorLookupKey("user", item.ID)] = firstReadableName(item.Name, item.RealName, item.Username)
+ }
+ var staff []models.StaffAccount
+ if err := load("staff", &staff); err != nil {
+ return nil, err
+ }
+ for _, item := range staff {
+ names[creatorLookupKey("staff", item.ID)] = firstReadableName(item.Name, item.Username)
+ }
+ var deliveries []models.DeliveryBasic
+ if err := load("delivery", &deliveries); err != nil {
+ return nil, err
+ }
+ for _, item := range deliveries {
+ names[creatorLookupKey("delivery", item.ID)] = item.Name
+ }
+ var stations []models.GasBasic
+ if err := load("gas", &stations); err != nil {
+ return nil, err
+ }
+ for _, item := range stations {
+ names[creatorLookupKey("gas", item.ID)] = item.Name
+ }
+ return names, nil
+}
+
+func firstReadableName(values ...string) string {
+ for _, value := range values {
+ if value = strings.TrimSpace(value); value != "" {
+ return value
+ }
+ }
+ return ""
+}
+
+// loadGasorderProductSummaries 生成“名称(类型)”或“名称(类型)等 N 个”的订单气瓶摘要。
+func loadGasorderProductSummaries(orders []models.GasorderBasic) (map[uint64]string, error) {
+ orderIDs := make([]uint64, 0, len(orders))
+ for _, order := range orders {
+ orderIDs = append(orderIDs, order.ID)
+ }
+ if len(orderIDs) == 0 {
+ return map[uint64]string{}, nil
+ }
+ var items []gasorderItemDisplay
+ if err := gasorderItemDisplayQuery(impl.DBService, "").
+ Where("gasorder_item.gasorder_basic_id IN ? AND gasorder_item.active = ?", orderIDs, true).
+ Order("gasorder_item.gasorder_basic_id asc, gasorder_item.id asc").Scan(&items).Error; err != nil {
+ return nil, err
+ }
+ type summary struct {
+ first string
+ count int
+ }
+ grouped := map[uint64]summary{}
+ for _, item := range items {
+ current := grouped[item.GasorderBasicID]
+ if current.count == 0 {
+ name := firstReadableName(item.ProductName, item.ProductCode, item.Identity)
+ if productType := strings.TrimSpace(item.ProductTypeName); productType != "" {
+ name += "(" + productType + ")"
+ }
+ current.first = name
+ }
+ current.count++
+ grouped[item.GasorderBasicID] = current
+ }
+ result := map[uint64]string{}
+ for orderID, item := range grouped {
+ result[orderID] = item.first
+ if item.count > 1 {
+ result[orderID] = fmt.Sprintf("%s等 %d 个", item.first, item.count)
+ }
+ }
+ return result, nil
+}
+
+// restoreGasorderListAddresses 仅在已鉴权的平台订单列表中恢复订单收货地址快照。
+func restoreGasorderListAddresses(response any, orders []models.GasorderBasic) any {
+ list, ok := response.([]any)
+ if !ok {
+ return response
+ }
+ for index, item := range list {
+ if index >= len(orders) {
+ break
+ }
+ if record, ok := item.(map[string]any); ok {
+ record["address"] = orders[index].Address
+ }
+ }
+ return response
+}
diff --git a/backend/api/internal/logic/platform/gasorder/order_list_display_test.go b/backend/api/internal/logic/platform/gasorder/order_list_display_test.go
new file mode 100644
index 0000000..50bb870
--- /dev/null
+++ b/backend/api/internal/logic/platform/gasorder/order_list_display_test.go
@@ -0,0 +1,38 @@
+// 功能描述:验证平台配送订单列表的可读名称、地址恢复和空值兜底规则。
+// 版本:v1.0.0
+package gasorder
+
+import (
+ "testing"
+
+ "git.apinb.com/heqiapp/platforms/backend/api/internal/models"
+)
+
+// TestFirstReadableName 验证创建方名称按首个非空业务名称降级。
+func TestFirstReadableName(t *testing.T) {
+ if got := firstReadableName("", " 张三 ", "staff-01"); got != "张三" {
+ t.Fatalf("首个可读名称 = %q,期望 张三", got)
+ }
+ if got := firstReadableName("", " "); got != "" {
+ t.Fatalf("全空名称应返回空字符串,实际为 %q", got)
+ }
+}
+
+// TestRestoreGasorderListAddresses 验证仅按分页记录顺序恢复已鉴权订单的地址快照。
+func TestRestoreGasorderListAddresses(t *testing.T) {
+ response := []any{
+ map[string]any{"identity": "order-1"},
+ map[string]any{"identity": "order-2"},
+ }
+ orders := []models.GasorderBasic{
+ {Address: "上海市浦东新区客户路 88 号"},
+ {Address: ""},
+ }
+ restored := restoreGasorderListAddresses(response, orders).([]any)
+ if got := restored[0].(map[string]any)["address"]; got != orders[0].Address {
+ t.Fatalf("订单地址 = %v,期望 %s", got, orders[0].Address)
+ }
+ if got := restored[1].(map[string]any)["address"]; got != "" {
+ t.Fatalf("空地址应保持为空,实际为 %v", got)
+ }
+}
diff --git a/docs/操作日志_配送订单列表可读信息优化_20260814.md b/docs/操作日志_配送订单列表可读信息优化_20260814.md
new file mode 100644
index 0000000..3b0847a
--- /dev/null
+++ b/docs/操作日志_配送订单列表可读信息优化_20260814.md
@@ -0,0 +1,44 @@
+# 配送订单列表可读信息优化操作日志
+
+操作时间:2026-08-14
+操作类型:修改
+影响模块:平台总后台、气体配送订单列表
+
+## 操作前状态
+
+- 创建方列直接显示唯一标识,管理员无法快速识别实际创建主体。
+- 收货地址已写入订单快照,但经过通用位置保护后列表显示为 `-`。
+- 合同气瓶是订单明细集合,通用列表没有生成可读摘要。
+
+## 具体操作
+
+- 为平台配送订单列表增加专用只读响应,批量解析用户、工作人员、配送站和气站的当前名称。
+- 在全局位置保护执行后,仅为已鉴权的平台订单列表恢复订单自身的收货地址快照。
+- 批量读取订单当前有效气瓶,生成“智能气阀名称(设备类型)”摘要;多只设备显示“等 N 个”。
+- 前端列表字段支持读取专用展示字段,合同气瓶摘要可直接进入订单详情。
+- 未扩展模糊搜索,未修改数据库结构和订单持久化字段。
+
+## 操作后状态
+
+- 创建方以当前可读名称展示;关联记录失效时显示“记录已失效(唯一标识)”。
+- 收货地址显示订单创建时保存的地址快照,空值显示“未填写”,长文本沿用表格悬浮全文能力。
+- 合同气瓶显示紧凑摘要,点击后可在详情页查看全部气瓶记录。
+
+## 代码变更
+
+- `backend/api/internal/logic/platform/gasorder/order_list_display.go`
+- `backend/api/internal/logic/platform/gasorder/order_list_display_test.go`
+- `backend/api/internal/logic/platform/gasorder/gasorder.go`
+- `frontend/platform_admin/src/api/resources.ts`
+- `frontend/platform_admin/src/views/shared/CrudListPage.vue`
+
+## 验证结果
+
+- 后端平台配送订单专项测试通过。
+- 平台总后台 TypeScript 类型检查与生产构建通过。
+
+## 风险评估
+
+- 创建方展示的是关联档案当前名称,档案改名后列表名称会同步变化;订单的创建方唯一标识保持不变。
+- 地址恢复仅位于平台管理端订单列表专用接口,不修改通用敏感字段保护规则。
+- 气瓶摘要只统计订单当前有效明细,完整历史仍以订单详情为准。
diff --git a/frontend/platform_admin/src/api/resources.ts b/frontend/platform_admin/src/api/resources.ts
index f6898be..6d08315 100644
--- a/frontend/platform_admin/src/api/resources.ts
+++ b/frontend/platform_admin/src/api/resources.ts
@@ -78,6 +78,10 @@ export type ResourceField = {
readonlyRelationText?: boolean;
/** 列表中以可悬浮、可复制的紧凑文本展示普通字段。 */
listCopyable?: boolean;
+ /** 列表读取另一个只读响应字段展示,表单仍使用原业务字段。 */
+ listDisplayKey?: string;
+ /** 点击列表展示内容时进入当前记录详情。 */
+ listDetailLink?: boolean;
options?: Array<{ label: string; value: string | number }>;
defaultValue?: string | number | boolean;
readonlyOnCreate?: boolean;
@@ -555,7 +559,7 @@ export const resources: ResourceUiDefinition[] = [
{ name: '解绑气瓶', resource: '/gasorder_contract_product/:identity/unbind', danger: true, fields: reason },
]),
define('gasorder_contract_revision', '合同修订记录', 'readonly', []),
- define('gasorder_basic', '气体配送订单', 'append_only', [f('request_no', { required: true }), relation('gasorder_contract_identity', '/gasorder_contract', true, { label: '配送合同', placeholder: '请选择当前可履约的生效合同', relationFilters: { candidate: 'order' }, relationStrictFilter: true, relationInvalidMessage: '所选配送合同已不可履约,已清空,请重新选择', relationEmptyText: '暂无可下单的生效合同,请先启用或续签配送合同' }), f('creator_type', { required: true, type: 'select', options: resourceSearchEnumOptions('gasorder_basic', 'creator_type') }), f('creator_identity', { label: '创建方', required: true, placeholder: '请先选择配送合同和创建方类型', readonlyRelationText: true, relationFilters: { status: '1' }, relationEmptyText: '当前合同下暂无可用的创建方', dynamicRelation: {
+ define('gasorder_basic', '气体配送订单', 'append_only', [f('request_no', { required: true }), relation('gasorder_contract_identity', '/gasorder_contract', true, { label: '配送合同', placeholder: '请选择当前可履约的生效合同', relationFilters: { candidate: 'order' }, relationStrictFilter: true, relationInvalidMessage: '所选配送合同已不可履约,已清空,请重新选择', relationEmptyText: '暂无可下单的生效合同,请先启用或续签配送合同' }), f('creator_type', { required: true, type: 'select', options: resourceSearchEnumOptions('gasorder_basic', 'creator_type') }), f('creator_identity', { label: '创建方', required: true, listDisplayKey: 'creator_display_name', placeholder: '请先选择配送合同和创建方类型', readonlyRelationText: true, relationFilters: { status: '1' }, relationEmptyText: '当前合同下暂无可用的创建方', dynamicRelation: {
parentKey: 'creator_type', parentLabel: '创建方类型', contextParentKey: 'gasorder_contract_identity',
resources: { user: '/user_account', staff: '/staff_account', delivery: '/delivery_basic', gas: '/gas_basic' },
fixedIdentityKeys: { user: 'user_account_identity', gas: 'gas_basic_identity', delivery: 'delivery_basic_identity' },
@@ -567,7 +571,7 @@ export const resources: ResourceUiDefinition[] = [
parentChangeMessage: '创建方类型已变更,已按当前合同重新匹配创建方',
contextChangeMessage: '配送合同已变更,创建方已按新合同重新匹配',
} }), relation('user_address_identity', '/user_address', true, {
- label: '收货地址', placeholder: '请先选择配送合同,再选择该合同用户的收货地址',
+ label: '收货地址', listDisplayKey: 'address', emptyText: '未填写', placeholder: '请先选择配送合同,再选择该合同用户的收货地址',
relationOptionLabelKey: 'address', relationOptionDefaultKey: 'is_default',
relationAutoSelectKey: 'is_default', relationEmptyText: '该合同用户暂无收货地址,请先维护用户地址',
relationLinkage: {
@@ -575,7 +579,7 @@ export const resources: ResourceUiDefinition[] = [
filterKey: 'gasorder_contract_identity', filterOnly: true, requiresParent: true,
parentChangeMessage: '配送合同已变更,请重新选择该合同用户的收货地址',
},
- }), f('gasorder_contract_product_identities', { label: '合同气瓶', required: true, type: 'identity-list', relation: '/gasorder_contract_product', placeholder: '请输入智能气阀名称、设备类型或设备编码搜索', relationOptionDisplay: 'product-name-type', relationEmptyText: '该合同暂无可用气瓶,请先为合同绑定气瓶', relationLinkage: { parentKey: 'gasorder_contract_identity', optionParentKey: '', filterKey: 'contract_identity', filterOnly: true, requiresParent: true, parentChangeMessage: '配送合同已变更,请重新选择该合同可用的气瓶' } }), f('contact_name', { required: true }), f('contact_phone', { required: true }), f('discount_amount'), f('remark')], 'list', [
+ }), f('gasorder_contract_product_identities', { label: '合同气瓶', required: true, type: 'identity-list', listDisplayKey: 'contract_products_summary', listDetailLink: true, emptyText: '未填写', relation: '/gasorder_contract_product', placeholder: '请输入智能气阀名称、设备类型或设备编码搜索', relationOptionDisplay: 'product-name-type', relationEmptyText: '该合同暂无可用气瓶,请先为合同绑定气瓶', relationLinkage: { parentKey: 'gasorder_contract_identity', optionParentKey: '', filterKey: 'contract_identity', filterOnly: true, requiresParent: true, parentChangeMessage: '配送合同已变更,请重新选择该合同可用的气瓶' } }), f('contact_name', { required: true }), f('contact_phone', { required: true }), f('discount_amount'), f('remark')], 'list', [
{ name: '分配订单', resource: '/gasorder_basic/:identity/assign', fields: [relation('delivery_basic_identity', '/delivery_basic', true), relation('staff_account_identity', '/staff_account', true, { staffRelation: { roles: ['delivery'], enabledOnly: true, workStatus: 'on_duty' } }), ...reason], visibleFor: { field: 'order_status', values: [16, 18] } },
{ name: '开始罐装', resource: '/gasorder_basic/:identity/filling', fields: reason, visibleFor: { field: 'order_status', values: [18] } },
{ name: '待配送', resource: '/gasorder_basic/:identity/ready', fields: reason, visibleFor: { field: 'order_status', values: [19] } },
diff --git a/frontend/platform_admin/src/views/shared/CrudListPage.vue b/frontend/platform_admin/src/views/shared/CrudListPage.vue
index 5dacfa6..ad88cc3 100644
--- a/frontend/platform_admin/src/views/shared/CrudListPage.vue
+++ b/frontend/platform_admin/src/views/shared/CrudListPage.vue
@@ -70,6 +70,9 @@
v-else-if="field.listCopyable && record[field.key]"
:value="String(record[field.key])"
/>
+
+ {{ displayListField(field, record) }}
+
{{ displayListField(field, record) }}
@@ -464,6 +467,10 @@ async function changePage(next: number) {
/** 使用资源静态选项或运行时平台角色名称显示列表字段。 */
function displayListField(field: ResourceField, row: ResourceRow) {
+ if (field.listDisplayKey) {
+ const value = row[field.listDisplayKey];
+ return value == null || value === '' ? (field.emptyText ?? '-') : String(value);
+ }
return displayResourceField(field, row, relations.options, fieldOptions);
}