完善配送订单创建联动

This commit is contained in:
czl231
2026-08-23 11:30:52 +08:00
parent c07c9ea6f2
commit 59a01192c0
15 changed files with 935 additions and 163 deletions

View File

@@ -0,0 +1,64 @@
/**
* 功能描述:计算配送订单创建页的只读金额预估并执行前端金额校验。
* 版本v1.0.0。
*/
import type { ResourceRow } from './resource-record-form';
export type OrderAmountPreview = {
productAmount: number;
deliveryFee: number;
discountAmount: number;
payableAmount: number;
ready: boolean;
};
/** 金额均以分计算,页面只展示预估,最终结果仍由服务端重新计算。 */
export function orderAmountPreview(
form: Record<string, any>,
relationOptions: Record<string, ResourceRow[]>,
): OrderAmountPreview {
const contractIdentity = String(form.gasorder_contract_identity ?? '');
const contract = (relationOptions['/gasorder_contract'] ?? []).find(
(row) => String(row.identity ?? '') === contractIdentity,
);
const selected = new Set(
(Array.isArray(form.gasorder_contract_product_identities)
? form.gasorder_contract_product_identities
: []
)
.map((value: unknown) => String(value ?? ''))
.filter(Boolean),
);
const products = (relationOptions['/gasorder_contract_product'] ?? []).filter(
(row) => selected.has(String(row.identity ?? '')),
);
const productAmount = products.reduce(
(total, row) => total + Math.max(0, Number(row.unit_price ?? 0)),
0,
);
const deliveryFee = Math.max(0, Number(contract?.default_delivery_fee ?? 0));
const discountAmount = Math.round(Number(form.discount_amount ?? 0) * 100);
return {
productAmount,
deliveryFee,
discountAmount,
payableAmount: productAmount + deliveryFee - discountAmount,
ready: Boolean(
contract && selected.size > 0 && products.length === selected.size,
),
};
}
/** 返回订单金额的前端校验结果;服务端仍执行相同约束和溢出保护。 */
export function orderAmountValidation(preview: OrderAmountPreview) {
if (!Number.isFinite(preview.discountAmount) || preview.discountAmount < 0)
return '优惠金额不能为负数';
if (preview.ready && preview.payableAmount <= 0)
return '优惠金额必须小于商品金额与配送费之和';
return '';
}
/** 将分格式化为人民币展示文本。 */
export function formatOrderMoney(value: number) {
return `¥${(value / 100).toFixed(2)}`;
}

View File

@@ -9,12 +9,28 @@ export type ResourceRow = Record<string, unknown>;
const editableFieldKeys: Record<string, Set<string>> = {
staff_account: new Set(['name', 'phone', 'avatar', 'work_status']),
staff_credential: new Set(['staff_account_identity', 'credential_type', 'credential_no', 'expired_at']),
staff_credential: new Set([
'staff_account_identity',
'credential_type',
'credential_no',
'expired_at',
]),
user_account: new Set(['name', 'phone', 'avatar', 'real_name']),
user_address: new Set(['user_account_identity', 'address', 'longitude', 'latitude', 'is_default']),
user_address: new Set([
'user_account_identity',
'address',
'longitude',
'latitude',
'is_default',
]),
gasorder_contract: new Set([
'title', 'terms', 'file_uri', 'default_delivery_fee',
'signed_at', 'effective_at', 'expired_at',
'title',
'terms',
'file_uri',
'default_delivery_fee',
'signed_at',
'effective_at',
'expired_at',
]),
};
@@ -60,13 +76,19 @@ export function validateResourceRecordForm(
mode: 'create' | 'edit',
) {
const required = fields.filter(
(field) => field.required && !(mode === 'edit' && field.type === 'password'),
(field) =>
field.required && !(mode === 'edit' && field.type === 'password'),
);
if (required.some((field) => isMissingField(form[field.key]))) return '请填写必填字段';
if (required.some((field) => isMissingField(form[field.key])))
return '请填写必填字段';
const password = fields.find(
(field) => field.type === 'password' && !isMissingField(form[field.key]),
);
if (password && Array.from(String(form[password.key])).length < 6)
return '密码长度不能少于 6 个字符';
const belowMinimum = fields.find(
(field) => field.min != null && Number(form[field.key]) < field.min,
);
if (belowMinimum) return `${belowMinimum.label}不能小于 ${belowMinimum.min}`;
return '';
}

View File

@@ -25,6 +25,14 @@ export type ResourceFieldType =
| 'select'
| 'contract-file';
/** 父子关联候选配置;筛选仍由服务端按当前配送点范围执行。 */
export type ResourceRelationLinkage = {
parentKey: string;
filterKey: string;
requiresParent?: boolean;
parentChangeMessage?: string;
};
export type ResourceField = {
key: string;
label: string;
@@ -39,6 +47,20 @@ export type ResourceField = {
readonlyRelationText?: boolean;
/** 列表中以可悬浮、可复制的紧凑文本展示普通字段。 */
listCopyable?: boolean;
/** 关联选项优先展示的业务字段。 */
relationOptionLabelKey?: string;
/** 合同气瓶等关系的组合展示方式。 */
relationOptionDisplay?: 'product-name-type';
/** 为默认候选追加中文标识,并在加载完成后自动选择。 */
relationOptionDefaultKey?: string;
relationAutoSelectKey?: string;
/** 关联候选为空时的业务提示。 */
relationEmptyText?: string;
relationFilters?: Record<string, string>;
relationStrictFilter?: boolean;
relationInvalidMessage?: string;
relationLinkage?: ResourceRelationLinkage;
min?: number;
options?: Array<{ label: string; value: string | number; disabled?: boolean }>;
defaultValue?: string | number | boolean;
readonly?: boolean;
@@ -530,7 +552,50 @@ const deliveryOverrides: ResourceUiDefinition[] = [
]),
define('gasorder_contract_revision', '合同修订记录', 'readonly', []),
define('product_info', '合同可选气瓶', 'readonly', []),
define('gasorder_basic', '配送订单', 'append_only', [f('request_no', { required: true }), relation('gasorder_contract_identity', '/gasorder_contract', true), relation('user_address_identity', '/user_address', true), f('gasorder_contract_product_identities', { required: true, type: 'identity-list', relation: '/gasorder_contract_product' }), f('contact_name', { required: true }), f('contact_phone', { required: true }), f('discount_amount'), f('remark')], 'list', [
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: '暂无可下单的生效合同,请先启用或续签配送合同',
}),
relation('user_address_identity', '/user_address', true, {
label: '收货地址',
placeholder: '请先选择配送合同,再选择该合同用户的收货地址',
relationOptionLabelKey: 'address',
relationOptionDefaultKey: 'is_default',
relationAutoSelectKey: 'is_default',
relationEmptyText: '该合同用户暂无收货地址,请先维护用户地址',
relationLinkage: {
parentKey: 'gasorder_contract_identity',
filterKey: 'gasorder_contract_identity',
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',
filterKey: 'contract_identity',
requiresParent: true,
parentChangeMessage: '配送合同已变更,请重新选择该合同可用的气瓶',
},
}),
f('contact_name', { required: true }),
f('contact_phone', { required: true }),
f('discount_amount', { defaultValue: 0, min: 0 }),
f('remark'),
], '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/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] } },