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