diff --git a/docs/05-平台总后台需求.md b/docs/05-平台总后台需求.md index ba242e6..c4ab850 100644 --- a/docs/05-平台总后台需求.md +++ b/docs/05-平台总后台需求.md @@ -202,6 +202,8 @@ 创建订单时必须提交唯一请求号、有效合同、创建方、用户地址、合同气瓶集合、联系人和联系方式。后端根据合同价格和默认配送费计算订单金额,不接受前端自行决定最终金额。 +平台创建订单时,创建方类型支持用户、工作人员、配送站和气站。创建方候选必须根据已选类型分别加载用户账户、工作人员账户、配送站或气站;未选择类型时禁用创建方字段,切换类型时清空原创建方并重新加载候选。候选以用户名或机构名称等业务名称展示,不直接要求管理员识别唯一标识。 + 创建订单页面必须先选择配送合同,收货地址下拉仅展示该合同签约用户的地址。地址选项以完整地址为主文案并标记默认地址;存在默认地址时自动选中,切换合同时清空原地址。若合同用户没有可用地址,页面应明确引导先维护用户地址并禁止提交。 合同气瓶下拉仅展示所选合同中尚未解绑的气瓶,按气瓶类型和气瓶编码排序,并支持按气瓶编码或类型搜索。选项以气瓶类型名称展示;切换合同时清空原选择。若合同没有可用气瓶,页面应引导先为合同绑定气瓶并禁止提交。合同气瓶管理列表仍保留完整绑定和解绑历史。 diff --git a/docs/项目文档_配送订单创建方动态联动_v1.0.md b/docs/项目文档_配送订单创建方动态联动_v1.0.md new file mode 100644 index 0000000..740c735 --- /dev/null +++ b/docs/项目文档_配送订单创建方动态联动_v1.0.md @@ -0,0 +1,49 @@ +# 项目文档_配送订单创建方动态联动_v1.0 + +## 1. 项目概述 + +本次修复平台总后台创建配送订单时“创建方标识”始终显示“暂无数据”的问题。后端已经支持四种创建方,但前端字段只有关系控件类型,没有绑定数据资源,也没有根据创建方类型切换候选。 + +## 2. 业务规则 + +- 创建方类型支持用户、工作人员、配送站和气站。 +- 未选择创建方类型时,创建方下拉框保持禁用并提示先选择类型。 +- 用户对应 `/user_account`。 +- 工作人员对应 `/staff_account`。 +- 配送站对应 `/delivery_basic`。 +- 气站对应 `/gas_basic`。 +- 切换创建方类型时清空原创建方,防止跨类型提交旧标识。 +- 候选显示用户名、人员显示名称或机构名称等现有业务字段。 +- 后端继续校验创建方类型与唯一标识,不修改数据库。 + +## 3. 核心文件 + +- `frontend/platform_admin/src/api/resources.ts`:声明动态关系配置和四类资源映射。 +- `frontend/platform_admin/src/views/resource/ResourceFieldForm.vue`:动态资源渲染、父类型禁用和选择事件。 +- `frontend/platform_admin/src/views/resource/use-resource-relation-linkage.ts`:动态候选加载、搜索与类型切换清理。 +- `frontend/platform_admin/scripts/check-gasorder-creator-linkage.mjs`:创建方动态联动专项检查。 + +## 4. 维护指南 + +- 新增创建方类型时必须同时更新后端 `gasorderCreatorModels` 和前端动态资源映射。 +- 不得把不同模型的唯一标识混放在同一个静态候选列表中。 +- 切换父类型必须先清空原值,再加载新类型候选。 +- 动态关系能力是通用扩展,其他多类型主体字段可复用,避免页面级硬编码。 + +## 5. 变更记录 + +操作时间:2026-08-14 + +操作类型:修复、扩展 + +影响模块:平台总后台、气体配送订单创建 + +操作前状态:创建方标识没有关系资源,选择创建方类型后仍显示“暂无数据”。 + +具体操作:增加父字段驱动的动态关系配置,接入候选加载、搜索、禁用和切换清理流程。 + +操作后状态:管理员选择创建方类型后可选择对应业务主体,切换类型会清空旧选择。 + +验证范围:前端专项检查、类型检查、生产构建和既有资源契约检查。 + +风险评估:后端接口和数据库不变;动态关系仅在显式配置字段启用,不影响既有固定关系字段。 diff --git a/frontend/platform_admin/package.json b/frontend/platform_admin/package.json index 94a4461..3e04509 100644 --- a/frontend/platform_admin/package.json +++ b/frontend/platform_admin/package.json @@ -25,6 +25,7 @@ "gasorder-address:check": "node scripts/check-gasorder-address-linkage.mjs", "gasorder-contract-candidates:check": "node scripts/check-gasorder-contract-candidates.mjs", "gasorder-contract-products:check": "node scripts/check-gasorder-contract-product-display.mjs", + "gasorder-creator:check": "node scripts/check-gasorder-creator-linkage.mjs", "product-ownership:check": "node scripts/check-product-ownership-action.mjs", "product-ownership-display:check": "node scripts/check-product-ownership-display.mjs", "product-lifecycle-display:check": "node scripts/check-product-lifecycle-display.mjs", diff --git a/frontend/platform_admin/scripts/check-gasorder-creator-linkage.mjs b/frontend/platform_admin/scripts/check-gasorder-creator-linkage.mjs new file mode 100644 index 0000000..a09f1c2 --- /dev/null +++ b/frontend/platform_admin/scripts/check-gasorder-creator-linkage.mjs @@ -0,0 +1,28 @@ +/** + * 功能:静态检查配送订单创建方类型与业务主体候选的动态联动。 + * 版本:v1.0.0 + */ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; + +const resources = readFileSync(new URL('../src/api/resources.ts', import.meta.url), 'utf8'); +const form = readFileSync(new URL('../src/views/resource/ResourceFieldForm.vue', import.meta.url), 'utf8'); +const linkage = readFileSync(new URL('../src/views/resource/use-resource-relation-linkage.ts', import.meta.url), 'utf8'); + +assert.match(resources, /parentKey: 'creator_type'/, '创建方必须由创建方类型驱动'); +for (const mapping of [ + "user: '/user_account'", + "staff: '/staff_account'", + "delivery: '/delivery_basic'", + "gas: '/gas_basic'", +]) { + assert.ok(resources.includes(mapping), `创建方动态资源缺少映射:${mapping}`); +} +assert.match(resources, /label: '创建方'/, '创建方字段必须使用业务名称'); +assert.match(resources, /创建方类型已变更,请重新选择创建方/, '切换类型时缺少清空提示'); +assert.match(form, /field\.dynamicRelation && !relationResource\(field\)/, '未选择类型时必须禁用创建方'); +assert.match(form, /v-else-if="field\.type === 'select'"[\s\S]*?emit\('change-relation'/, '普通选择框未触发动态联动'); +assert.match(linkage, /reloadDynamicOptions/, '动态关系候选未接入加载流程'); +assert.match(linkage, /form\[childField\.key\] = ''/, '切换类型时未清空旧创建方'); + +console.log('配送订单创建方检查通过:四类创建方按类型动态加载,切换类型会清空旧值。'); diff --git a/frontend/platform_admin/src/api/resources.ts b/frontend/platform_admin/src/api/resources.ts index f779051..e5e0a43 100644 --- a/frontend/platform_admin/src/api/resources.ts +++ b/frontend/platform_admin/src/api/resources.ts @@ -39,6 +39,14 @@ export type ResourceRelationLinkage = { parentChangeMessage?: string; }; +/** 根据父字段值选择关系资源,用于创建方等多类型业务主体。 */ +export type ResourceDynamicRelation = { + parentKey: string; + parentLabel: string; + resources: Record; + parentChangeMessage?: string; +}; + export type ResourceField = { key: string; label: string; @@ -60,6 +68,8 @@ export type ResourceField = { readonlyOnCreate?: boolean; unknownValueLabel?: string; relationLinkage?: ResourceRelationLinkage; + /** 父字段值决定实际关系资源,未选择父字段时不加载候选。 */ + dynamicRelation?: ResourceDynamicRelation; /** 关联选项优先使用的业务展示字段。 */ relationOptionLabelKey?: string; /** 该字段为真时,在关联选项后追加“默认地址”。 */ @@ -523,7 +533,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', { required: true }), relation('user_address_identity', '/user_address', true, { + 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: '请选择创建方', relationEmptyText: '当前类型暂无可用的创建方数据', dynamicRelation: { parentKey: 'creator_type', parentLabel: '创建方类型', resources: { user: '/user_account', staff: '/staff_account', delivery: '/delivery_basic', gas: '/gas_basic' }, parentChangeMessage: '创建方类型已变更,请重新选择创建方' } }), relation('user_address_identity', '/user_address', true, { label: '收货地址', placeholder: '请先选择配送合同,再选择该合同用户的收货地址', relationOptionLabelKey: 'address', relationOptionDefaultKey: 'is_default', relationAutoSelectKey: 'is_default', relationEmptyText: '该合同用户暂无收货地址,请先维护用户地址', @@ -619,3 +629,14 @@ export function getResource(resourcePath: string): ResourceUiDefinition { if (!definition) throw new Error(`未知资源:${resourcePath}`); return definition; } + +/** 返回动态关系字段当前应使用的资源;父值为空或未知时返回空字符串。 */ +export function dynamicRelationResource( + field: ResourceField, + values: Record, +) { + const dynamic = field.dynamicRelation; + if (!dynamic) return field.relation ?? ''; + const parentValue = String(values[dynamic.parentKey] ?? '').trim(); + return dynamic.resources[parentValue] ?? ''; +} diff --git a/frontend/platform_admin/src/views/resource/ResourceFieldForm.vue b/frontend/platform_admin/src/views/resource/ResourceFieldForm.vue index eed78ba..0168b4e 100644 --- a/frontend/platform_admin/src/views/resource/ResourceFieldForm.vue +++ b/frontend/platform_admin/src/views/resource/ResourceFieldForm.vue @@ -1,6 +1,6 @@