完善配送订单列表可读信息

This commit is contained in:
czl231
2026-08-25 23:32:58 +08:00
parent 59a01192c0
commit 44014a0e4f
7 changed files with 154 additions and 11 deletions

View File

@@ -1,5 +1,5 @@
// 功能描述:实现配送点范围内的合同、合同气瓶和配送订单接口。 // 功能描述:实现配送点范围内的合同、合同气瓶和配送订单接口。
// 版本v1.2.0。 // 版本v1.3.0。
package delivery package delivery
import ( import (
@@ -364,10 +364,30 @@ func scopedOrder(ctx *gin.Context, identity string, pointID uint64) (models.Gaso
func ListOrder(ctx *gin.Context) { func ListOrder(ctx *gin.Context) {
point, _, ok := currentScope(ctx) point, _, ok := currentScope(ctx)
if ok { if !ok {
listScoped(ctx, &models.GasorderBasic{}, common.ActiveRecords(db().Model(&models.GasorderBasic{})). return
Where("delivery_basic_id = ?", point.ID), "gasorder_basic.created_at desc")
} }
page, size := common.PageSize(ctx)
query := common.ApplyKeywordFilter(ctx,
common.ActiveRecords(db().Model(&models.GasorderBasic{})).Where("delivery_basic_id = ?", point.ID),
&models.GasorderBasic{},
)
var total int64
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
var orders []models.GasorderBasic
if err := query.Order("gasorder_basic.created_at desc").Offset((page - 1) * size).Limit(size).Find(&orders).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
response, err := platformgasorder.BuildGasorderListResponse(ctx, orders)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"total": total, "list": response})
} }
func GetOrder(ctx *gin.Context) { func GetOrder(ctx *gin.Context) {

View File

@@ -1,5 +1,5 @@
// 功能描述:为平台配送订单列表与详情补充关联名称、地址和合同气瓶摘要。 // 功能描述:为平台配送订单列表与详情补充关联名称、地址和合同气瓶摘要。
// 版本v1.1.0 // 版本v1.2.0
package gasorder package gasorder
import ( import (
@@ -62,18 +62,27 @@ func ListGasorderBasic(ctx *gin.Context) {
infra.Response.Error(ctx, err) infra.Response.Error(ctx, err)
return return
} }
displays, err := buildGasorderListDisplays(orders) response, err := BuildGasorderListResponse(ctx, orders)
if err != nil { if err != nil {
infra.Response.Error(ctx, err) infra.Response.Error(ctx, err)
return return
} }
infra.Response.Success(ctx, gin.H{"total": total, "list": response})
}
// BuildGasorderListResponse 为已经完成数据范围筛选的订单构造安全、可读的管理端列表响应。
// 调用方必须先限定自身可见订单;本函数只补充派生名称、气瓶摘要并恢复订单地址快照。
func BuildGasorderListResponse(ctx *gin.Context, orders []models.GasorderBasic) (any, error) {
displays, err := buildGasorderListDisplays(orders)
if err != nil {
return nil, err
}
response, err := common.PublicResourceResponse(displays) response, err := common.PublicResourceResponse(displays)
if err != nil { if err != nil {
infra.Response.Error(ctx, err) return nil, err
return
} }
protected := common.ProtectPreciseLocation(ctx, &models.GasorderBasic{}, response) protected := common.ProtectPreciseLocation(ctx, &models.GasorderBasic{}, response)
infra.Response.Success(ctx, gin.H{"total": total, "list": restoreGasorderListAddresses(protected, orders)}) return restoreGasorderListAddresses(protected, orders), nil
} }
// buildGasorderListDisplays 批量读取创建方与有效订单气瓶,避免列表逐行查询。 // buildGasorderListDisplays 批量读取创建方与有效订单气瓶,避免列表逐行查询。

View File

@@ -0,0 +1,40 @@
# 操作日志:配送点订单列表可读信息
操作时间2026-08-25
操作类型:扩展、修改
影响模块5176 配送订单列表、配送订单列表接口
## 操作前状态
- 收货地址快照经过通用位置保护后,列表显示为 `-`
- 合同气瓶属于订单明细集合,通用列表响应没有可读摘要。
- 5173 已有受控地址恢复和气瓶摘要,但 5176 仍使用通用列表函数。
## 具体操作
1. 把 5173 已有订单列表展示构造过程封装为共享函数。
2. 配送端先限定当前配送点订单,再调用共享函数生成安全响应。
3. 前端字段声明读取 `address``contract_products_summary` 派生字段。
4. 合同气瓶摘要增加订单详情入口,空值统一显示“未填写”。
5. 增加静态契约检查,防止以后退回裸关联字段或横线展示。
## 行为变化
- 变更前:收货地址和合同气瓶均显示 `-`
- 变更后:地址显示订单快照;气瓶显示可读摘要并可进入订单详情。
- 兼容性:不修改数据库、创建订单请求和既有路由,只扩展已鉴权列表响应。
## 验证结果
- `go test ./api/internal/logic/delivery ./api/internal/logic/platform/gasorder`:通过。
- `npm.cmd run type:check`:通过。
- `npm.cmd run resource-pages:check`:通过,详情 17、新建 9、编辑 5。
- `npm.cmd run contract:check`通过18 个资源。
- `npm.cmd run build`通过2648 个模块完成生产构建。
- 后端已重新构建并恢复监听 `12426`,旧程序已备份为 `platform-api.before-order-list-20260825-232713.exe`
## 风险评估
- 地址属于敏感数据:仅对当前配送点已授权订单恢复快照,不影响公共接口。
- 气瓶当前名称可能随设备档案改名变化;类型和编码仍使用订单快照。
- 完整气瓶信息继续以订单详情为准,列表只提供紧凑摘要。

View File

@@ -0,0 +1,43 @@
# 项目文档:配送点订单列表可读信息 v1.0
## 1. 项目概述
5176 配送点管理端订单列表复用 5173 的受控展示规则,在当前配送点数据范围内显示订单收货地址快照和合同气瓶摘要。数据库、订单创建协议及通用敏感字段保护规则保持不变。
## 2. 核心实现
```text
backend/api/internal/logic/
├── delivery/order.go # 先限定当前配送点,再构造订单列表响应
└── platform/gasorder/order_list_display.go # 统一生成地址和气瓶可读摘要
frontend/delivery_admin/src/
├── api/resources.ts # 声明列表派生展示字段
└── views/shared/ResourceListPage.vue # 渲染只读摘要和详情入口
```
## 3. 展示规则
- 收货地址显示订单创建时保存的地址快照;空快照显示“未填写”。
- 合同气瓶显示第一只气瓶的当前智能气阀名称和下单时类型快照;多只气瓶追加“等 N 个”。
- 合同气瓶摘要可进入订单详情查看完整明细。
- 地址和气瓶摘要均为只读派生字段,不参与新建或业务动作提交。
## 4. 安全边界
- 配送端必须先按当前登录账号所属配送点筛选订单,再调用共享展示构造器。
- 地址恢复只作用于已经鉴权、已经完成配送点范围过滤的订单分页结果。
- 气瓶摘要只按当前页订单主键批量查询,不接受前端提供任意订单主键。
- 通用资源响应的地址、经纬度保护规则不变。
## 5. 验证
```text
go test ./api/internal/logic/delivery ./api/internal/logic/platform/gasorder
npm.cmd run type:check
npm.cmd run resource-pages:check
npm.cmd run build
```
## 6. 变更记录
- v1.02026-08-25修复配送点订单列表收货地址和合同气瓶显示为横线的问题。

View File

@@ -138,5 +138,9 @@ assert(recordPage.includes('if (saving.value) return'), '订单保存缺少显
assert(orderCreation.includes('productAmount + deliveryFee - discountAmount'), '订单金额预估公式不正确'); assert(orderCreation.includes('productAmount + deliveryFee - discountAmount'), '订单金额预估公式不正确');
assert(orderCreation.includes("优惠金额必须小于商品金额与配送费之和"), '优惠金额缺少应付金额正数校验'); assert(orderCreation.includes("优惠金额必须小于商品金额与配送费之和"), '优惠金额缺少应付金额正数校验');
assert(orderPreview.includes('金额仅供预览'), '订单金额预估未声明服务端最终计算'); assert(orderPreview.includes('金额仅供预览'), '订单金额预估未声明服务端最终计算');
assert(definitions.includes("listDisplayKey: 'address'"), '订单列表收货地址未使用受控地址快照');
assert(definitions.includes("listDisplayKey: 'contract_products_summary'"), '订单列表合同气瓶未使用可读摘要');
assert(listPage.includes('function displayListField'), '资源列表未支持后端派生展示字段');
assert(listPage.includes('field.listDetailLink'), '订单气瓶摘要缺少详情入口');
console.log(`独立资源页面契约通过:详情 ${listResources.length},新建 ${creatable.size},编辑 ${editable.size}`); console.log(`独立资源页面契约通过:详情 ${listResources.length},新建 ${creatable.size},编辑 ${editable.size}`);

View File

@@ -49,6 +49,10 @@ export type ResourceField = {
listCopyable?: boolean; listCopyable?: boolean;
/** 关联选项优先展示的业务字段。 */ /** 关联选项优先展示的业务字段。 */
relationOptionLabelKey?: string; relationOptionLabelKey?: string;
/** 列表使用后端派生的只读展示字段,不改变提交字段。 */
listDisplayKey?: string;
/** 列表摘要可直接进入当前记录详情。 */
listDetailLink?: boolean;
/** 合同气瓶等关系的组合展示方式。 */ /** 合同气瓶等关系的组合展示方式。 */
relationOptionDisplay?: 'product-name-type'; relationOptionDisplay?: 'product-name-type';
/** 为默认候选追加中文标识,并在加载完成后自动选择。 */ /** 为默认候选追加中文标识,并在加载完成后自动选择。 */
@@ -564,6 +568,9 @@ const deliveryOverrides: ResourceUiDefinition[] = [
}), }),
relation('user_address_identity', '/user_address', true, { relation('user_address_identity', '/user_address', true, {
label: '收货地址', label: '收货地址',
listDisplayKey: 'address',
listDetailLink: false,
emptyText: '未填写',
placeholder: '请先选择配送合同,再选择该合同用户的收货地址', placeholder: '请先选择配送合同,再选择该合同用户的收货地址',
relationOptionLabelKey: 'address', relationOptionLabelKey: 'address',
relationOptionDefaultKey: 'is_default', relationOptionDefaultKey: 'is_default',
@@ -581,6 +588,9 @@ const deliveryOverrides: ResourceUiDefinition[] = [
required: true, required: true,
type: 'identity-list', type: 'identity-list',
relation: '/gasorder_contract_product', relation: '/gasorder_contract_product',
listDisplayKey: 'contract_products_summary',
listDetailLink: true,
emptyText: '未填写',
placeholder: '请输入智能气阀名称、气瓶类型或气瓶编码搜索', placeholder: '请输入智能气阀名称、气瓶类型或气瓶编码搜索',
relationOptionDisplay: 'product-name-type', relationOptionDisplay: 'product-name-type',
relationEmptyText: '该合同暂无可用气瓶,请先为合同绑定气瓶', relationEmptyText: '该合同暂无可用气瓶,请先为合同绑定气瓶',

View File

@@ -1,4 +1,4 @@
<!-- 功能描述展示配送点标准资源列表并导航到独立记录页版本v1.1.0 --> <!-- 功能描述展示配送点标准资源列表并导航到独立记录页版本v1.2.0 -->
<template> <template>
<a-card :title="listTitle" :bordered="false"> <a-card :title="listTitle" :bordered="false">
<template v-if="isCredentialList" #extra> <template v-if="isCredentialList" #extra>
@@ -75,11 +75,15 @@
@click="previewListContractAttachment(record)" @click="previewListContractAttachment(record)"
>查看附件</a-button> >查看附件</a-button>
<span v-else-if="field.type === 'contract-file'">暂无附件</span> <span v-else-if="field.type === 'contract-file'">暂无附件</span>
<a-link v-else-if="field.listDetailLink" @click="openRecord('detail', record)">
{{ displayListField(field, record) }}
</a-link>
<template v-else-if="field.listDisplayKey">{{ displayListField(field, record) }}</template>
<IdentityText <IdentityText
v-else-if="(field.type === 'identity' || field.listCopyable) && fieldValue(field, record)" v-else-if="(field.type === 'identity' || field.listCopyable) && fieldValue(field, record)"
:value="fieldValue(field, record)" :value="fieldValue(field, record)"
/> />
<template v-else>{{ displayResourceValue(definition, field.key, record[field.key] ?? record[`${field.key}_masked`]) }}</template> <template v-else>{{ displayListField(field, record) }}</template>
</template> </template>
</a-table-column> </a-table-column>
<a-table-column title="状态" :width="100"> <a-table-column title="状态" :width="100">
@@ -245,6 +249,19 @@ function fieldValue(field: ResourceField, row: ResourceRow) {
return String(row[field.key] ?? row[`${field.key}_masked`] ?? ''); return String(row[field.key] ?? row[`${field.key}_masked`] ?? '');
} }
/** 优先使用后端受控派生字段展示列表摘要,空值使用字段业务文案。 */
function displayListField(field: ResourceField, row: ResourceRow) {
if (field.listDisplayKey) {
const value = row[field.listDisplayKey];
return value == null || value === '' ? (field.emptyText ?? '-') : String(value);
}
return displayResourceValue(
props.definition,
field.key,
row[field.key] ?? row[`${field.key}_masked`],
);
}
/** 加载当前列表和来源关系筛选。 */ /** 加载当前列表和来源关系筛选。 */
async function load() { async function load() {
avatarLoader.reset(); avatarLoader.reset();