完善订单金额调整上下文

This commit is contained in:
czl231
2026-08-29 22:29:15 +08:00
parent 965e12cbcc
commit 9bbe25fa79
7 changed files with 97 additions and 4 deletions

View File

@@ -0,0 +1,35 @@
# 操作日志:配送点订单金额调整上下文
操作时间2026-08-29
操作类型:修改
影响模块5176 订单详情、业务动作弹窗
## 操作前状态
- 订单详情已显示当前配送费和优惠金额。
- 打开“调整金额”时两个金额输入框为空,操作员缺少当前值上下文。
## 具体操作
1. 动作字段增加显式 `actionContextKey`,只读取声明过的当前记录字段。
2. 详情页将当前订单记录传入动作弹窗。
3. 金额初始值由分转换为元,避免预填后再次提交造成金额放大 100 倍。
4. 静态检查覆盖两个金额字段、当前记录传递和分转元处理。
## 行为变化
- 变更前:调整金额弹窗的配送费、优惠金额为空。
- 变更后:弹窗显示当前值,例如服务端 `100` 分显示为 `1.00` 元。
- 原因保持空白且必填,其他业务动作不受影响。
## 验证结果
- `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 个模块完成生产构建。
## 风险评估
- 仅显式声明的动作字段读取上下文,避免历史原因或无关字段被自动带入。
- 金额单位转换与现有提交协议对称,不修改后端接口。

View File

@@ -58,6 +58,8 @@ frontend/delivery_admin/
创建配送订单时以配送合同为父级收货地址只加载合同签约用户地址合同气瓶只加载当前未解绑绑定切换合同清空旧值并丢弃过期响应。候选为空或仍在加载时禁止保存金额区只提供预估最终金额继续由服务端计算。详细规则见《项目文档_配送点订单创建联动_v1.0.md》。 创建配送订单时以配送合同为父级收货地址只加载合同签约用户地址合同气瓶只加载当前未解绑绑定切换合同清空旧值并丢弃过期响应。候选为空或仍在加载时禁止保存金额区只提供预估最终金额继续由服务端计算。详细规则见《项目文档_配送点订单创建联动_v1.0.md》。
订单详情的“调整金额”动作读取当前配送费和优惠金额作为弹窗初始值,并完成分到元的展示转换;本次操作原因继续单独填写,不复用历史原因。
配送人员资质列表必须由配送人员列表进入。页面先通过受保护人员详情接口回查姓名,再以当前气站、配送点、`delivery` 角色和人员 ID 四重条件加载资质;缺少、无效或越权人员上下文时停止加载,绝不回退为全部资质。标题和上下文区域显示人员姓名及可复制唯一标识,表格隐藏重复人员列。新建页自动预填所属人员,创建和编辑均锁定该关系,返回地址只接受安全站内路径。 配送人员资质列表必须由配送人员列表进入。页面先通过受保护人员详情接口回查姓名,再以当前气站、配送点、`delivery` 角色和人员 ID 四重条件加载资质;缺少、无效或越权人员上下文时停止加载,绝不回退为全部资质。标题和上下文区域显示人员姓名及可复制唯一标识,表格隐藏重复人员列。新建页自动预填所属人员,创建和编辑均锁定该关系,返回地址只接受安全站内路径。
资源搜索由后端 `searchFields` 契约驱动。没有有效字段的配送点资料、用户地址和银行卡页面不渲染搜索区域;其他页面显示“可搜索:具体字段”提示。枚举字段同时接受中文展示名称和内部稳定编码,查询、分页和重置始终保留人员上下文及安全来源参数。 资源搜索由后端 `searchFields` 契约驱动。没有有效字段的配送点资料、用户地址和银行卡页面不渲染搜索区域;其他页面显示“可搜索:具体字段”提示。枚举字段同时接受中文展示名称和内部稳定编码,查询、分页和重置始终保留人员上下文及安全来源参数。

View File

@@ -0,0 +1,36 @@
# 项目文档:配送点订单金额调整上下文 v1.0
## 1. 项目概述
5176 配送点后台的“调整金额”弹窗从当前订单详情读取配送费和优惠金额,按页面金额单位预填,操作员只需在现值基础上修改并填写原因。
## 2. 展示与提交规则
- 当前详情响应中的金额单位为分,动作输入框单位为元。
- 打开弹窗时将 `delivery_fee``discount_amount` 分别除以 100 后预填。
- 保存时沿用现有表单协议,将元乘以 100 后提交服务端。
- “原因”不从历史记录预填,仍要求本次操作单独填写。
- 标记异常、取消订单等其他动作不读取无关字段,不受本次修改影响。
## 3. 核心文件
```text
frontend/delivery_admin/src/
├── api/resources.ts # 声明动作上下文字段
└── views/resource/
├── ResourceRecordPage.vue # 传入当前订单记录
└── ResourceActionDialog.vue # 读取上下文并执行分转元
```
## 4. 验证
```text
npm.cmd run type:check
npm.cmd run resource-pages:check
npm.cmd run contract:check
npm.cmd run build
```
## 5. 变更记录
- v1.02026-08-29调整金额弹窗预填当前配送费和优惠金额。

View File

@@ -67,6 +67,7 @@ assert(routeKeys.has('POST /gasorder_contract/attachment/upload'), '配送合同
assert(routeKeys.has('POST /gasorder_contract/attachment/cleanup'), '配送合同缺少临时附件清理接口'); assert(routeKeys.has('POST /gasorder_contract/attachment/cleanup'), '配送合同缺少临时附件清理接口');
assert(routeKeys.has('GET /gasorder_contract/:identity/attachment'), '配送合同缺少受控附件读取接口'); assert(routeKeys.has('GET /gasorder_contract/:identity/attachment'), '配送合同缺少受控附件读取接口');
const recordPage = read('src/views/resource/ResourceRecordPage.vue'); const recordPage = read('src/views/resource/ResourceRecordPage.vue');
const actionDialog = read('src/views/resource/ResourceActionDialog.vue');
assert(recordPage.includes('ResourceAccountSummary'), '账户资源页尚未接入 5173 头像摘要卡片'); assert(recordPage.includes('ResourceAccountSummary'), '账户资源页尚未接入 5173 头像摘要卡片');
assert(recordPage.includes("field.key !== 'avatar'"), '头像字段仍可能显示为普通文本框'); assert(recordPage.includes("field.key !== 'avatar'"), '头像字段仍可能显示为普通文本框');
const listPage = read('src/views/shared/ResourceListPage.vue'); const listPage = read('src/views/shared/ResourceListPage.vue');
@@ -142,5 +143,9 @@ assert(definitions.includes("listDisplayKey: 'address'"), '订单列表收货地
assert(definitions.includes("listDisplayKey: 'contract_products_summary'"), '订单列表合同气瓶未使用可读摘要'); assert(definitions.includes("listDisplayKey: 'contract_products_summary'"), '订单列表合同气瓶未使用可读摘要');
assert(listPage.includes('function displayListField'), '资源列表未支持后端派生展示字段'); assert(listPage.includes('function displayListField'), '资源列表未支持后端派生展示字段');
assert(listPage.includes('field.listDetailLink'), '订单气瓶摘要缺少详情入口'); assert(listPage.includes('field.listDetailLink'), '订单气瓶摘要缺少详情入口');
assert(definitions.includes("actionContextKey: 'delivery_fee'"), '调整金额未读取当前配送费上下文');
assert(definitions.includes("actionContextKey: 'discount_amount'"), '调整金额未读取当前优惠金额上下文');
assert(recordPage.includes(':context-record="record"'), '业务动作弹窗未接收当前详情记录');
assert(actionDialog.includes("field.type !== 'money'"), '金额动作上下文缺少分转元处理');
console.log(`独立资源页面契约通过:详情 ${listResources.length},新建 ${creatable.size},编辑 ${editable.size}`); console.log(`独立资源页面契约通过:详情 ${listResources.length},新建 ${creatable.size},编辑 ${editable.size}`);

View File

@@ -1,6 +1,6 @@
/** /**
* 功能描述:定义配送点后台资源能力、字段契约和受控业务动作。 * 功能描述:定义配送点后台资源能力、字段契约和受控业务动作。
* 版本v1.1.0。 * 版本v1.2.0。
*/ */
import { resourceSearchFields, type ResourceSearchField } from './resource-search-contract'; import { resourceSearchFields, type ResourceSearchField } from './resource-search-contract';
@@ -67,6 +67,8 @@ export type ResourceField = {
min?: number; min?: number;
options?: Array<{ label: string; value: string | number; disabled?: boolean }>; options?: Array<{ label: string; value: string | number; disabled?: boolean }>;
defaultValue?: string | number | boolean; defaultValue?: string | number | boolean;
/** 业务动作弹窗从当前详情记录读取初始值的字段键。 */
actionContextKey?: string;
readonly?: boolean; readonly?: boolean;
unknownValueLabel?: string; unknownValueLabel?: string;
}; };
@@ -608,7 +610,7 @@ const deliveryOverrides: ResourceUiDefinition[] = [
], 'list', [ ], 'list', [
{ name: '分配/改派', resource: '/gasorder_basic/:identity/assign', fields: [relation('staff_account_identity', '/staff_account', true), ...reason], visibleFor: { field: 'order_status', values: [16, 18] } }, { name: '分配/改派', resource: '/gasorder_basic/:identity/assign', fields: [relation('staff_account_identity', '/staff_account', true), ...reason], visibleFor: { field: 'order_status', values: [16, 18] } },
{ name: '回收任务', resource: '/gasorder_basic/:identity/reclaim', danger: true, fields: reason, visibleFor: { field: 'order_status', values: [18] } }, { name: '回收任务', resource: '/gasorder_basic/:identity/reclaim', danger: true, fields: reason, visibleFor: { field: 'order_status', values: [18] } },
{ name: '调整金额', resource: '/gasorder_basic/:identity/adjust-amount', fields: [f('delivery_fee', { required: true }), f('discount_amount', { required: true }), ...reason], visibleFor: { field: 'order_status', values: [16, 18] } }, { name: '调整金额', resource: '/gasorder_basic/:identity/adjust-amount', fields: [f('delivery_fee', { required: true, actionContextKey: 'delivery_fee' }), f('discount_amount', { required: true, actionContextKey: 'discount_amount' }), ...reason], visibleFor: { field: 'order_status', values: [16, 18] } },
{ name: '标记异常', resource: '/gasorder_basic/:identity/exception', fields: reason, visibleFor: { field: 'order_status', values: [18, 19, 20, 33, 34] } }, { name: '标记异常', resource: '/gasorder_basic/:identity/exception', fields: reason, visibleFor: { field: 'order_status', values: [18, 19, 20, 33, 34] } },
{ name: '恢复订单', resource: '/gasorder_basic/:identity/recover', fields: reason, visibleFor: { field: 'order_status', values: [21] } }, { name: '恢复订单', resource: '/gasorder_basic/:identity/recover', fields: reason, visibleFor: { field: 'order_status', values: [21] } },
{ name: '取消订单', resource: '/gasorder_basic/:identity/cancel', danger: true, fields: reason, visibleFor: { field: 'order_status', values: [16, 18] } }, { name: '取消订单', resource: '/gasorder_basic/:identity/cancel', danger: true, fields: reason, visibleFor: { field: 'order_status', values: [16, 18] } },

View File

@@ -1,4 +1,4 @@
<!-- 功能描述承载独立详情页的状态审核和专用业务动作版本v1.0.0 --> <!-- 功能描述承载独立详情页的状态审核和专用业务动作版本v1.1.0 -->
<template> <template>
<a-modal <a-modal
:visible="visible" :visible="visible"
@@ -37,6 +37,7 @@ const props = defineProps<{
action?: DetailAction; action?: DetailAction;
identity: string; identity: string;
currentStatus?: number; currentStatus?: number;
contextRecord?: ResourceRow;
relationOptions: Record<string, ResourceRow[]>; relationOptions: Record<string, ResourceRow[]>;
relationLoading: Record<string, boolean>; relationLoading: Record<string, boolean>;
}>(); }>();
@@ -54,7 +55,7 @@ watch(
if (!visible || !action) return; if (!visible || !action) return;
for (const key of Object.keys(form)) delete form[key]; for (const key of Object.keys(form)) delete form[key];
for (const field of action.fields ?? []) for (const field of action.fields ?? [])
form[field.key] = field.defaultValue; form[field.key] = actionInitialValue(field);
if ( if (
action.resource.endsWith('/:identity/status') && action.resource.endsWith('/:identity/status') &&
[1, 2].includes(Number(props.currentStatus)) [1, 2].includes(Number(props.currentStatus))
@@ -64,6 +65,17 @@ watch(
{ immediate: true }, { immediate: true },
); );
/** 从当前详情记录读取动作初始值;金额由服务端“分”转换为输入框使用的“元”。 */
function actionInitialValue(field: ResourceField) {
if (field.defaultValue !== undefined) return field.defaultValue;
if (!field.actionContextKey) return undefined;
const value = props.contextRecord?.[field.actionContextKey];
if (value == null || value === '') return undefined;
if (field.type !== 'money') return value;
const cents = Number(value);
return Number.isFinite(cents) ? cents / 100 : undefined;
}
/** 校验并执行当前业务动作。 */ /** 校验并执行当前业务动作。 */
async function submit() { async function submit() {
const action = props.action; const action = props.action;

View File

@@ -147,6 +147,7 @@
:action="activeAction" :action="activeAction"
:identity="identity" :identity="identity"
:current-status="Number(record.status)" :current-status="Number(record.status)"
:context-record="record"
:relation-options="relationOptions" :relation-options="relationOptions"
:relation-loading="relationLoading" :relation-loading="relationLoading"
@search-relation="searchRelation" @search-relation="searchRelation"