完善智能气阀新建归属选择
新建页面明确说明当前归属四选一,选择新归属时自动清空其他归属;字段改用业务名称并补充选择提示,同时扩展专项检查和中文文档。
This commit is contained in:
41
docs/操作日志_智能气阀新建归属互斥_20260813.md
Normal file
41
docs/操作日志_智能气阀新建归属互斥_20260813.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# 智能气阀新建归属互斥操作日志
|
||||||
|
|
||||||
|
操作时间:2026-08-13
|
||||||
|
操作类型:修改
|
||||||
|
影响模块:平台总后台智能气阀新建页面
|
||||||
|
|
||||||
|
## 操作前状态
|
||||||
|
|
||||||
|
新建智能气阀时,库房、气站、配送点和用户使用“唯一标识”技术名称,空下拉框缺少明确选择提示;四个互斥归属可以在前端同时选择,直到后端保存校验时才被拒绝。
|
||||||
|
|
||||||
|
## 具体操作
|
||||||
|
|
||||||
|
1. 字段标题改为“库房”“气站”“配送点”“用户”。
|
||||||
|
2. 新建表单分别显示“请选择库房”等引导文案。
|
||||||
|
3. 选择任一非空归属后自动清空另外三个归属。
|
||||||
|
4. 在新建表单顶部明确提示当前归属四选一及自动清空规则。
|
||||||
|
5. 保留通用关系联动调用和后端最终合法性校验。
|
||||||
|
6. 扩展既有专项检查,覆盖新建页面互斥行为、说明文案与展示配置。
|
||||||
|
|
||||||
|
## 操作后状态
|
||||||
|
|
||||||
|
管理员进入新建页面即可看到四选一说明,只需选择一个目标归属;切换归属类型时旧值自动清空。详情空值仍显示“未填写”。
|
||||||
|
|
||||||
|
## 代码变更
|
||||||
|
|
||||||
|
- `frontend/platform_admin/src/views/resource/ResourceRecordPage.vue`:接入新建页归属互斥处理。
|
||||||
|
- `frontend/platform_admin/src/api/resources.ts`:配置业务字段名和选择提示。
|
||||||
|
- `frontend/platform_admin/scripts/check-product-ownership-action.mjs`:覆盖新建页互斥行为。
|
||||||
|
- `frontend/platform_admin/scripts/check-product-ownership-display.mjs`:覆盖字段名、空值和提示文案。
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
|
||||||
|
- `npm.cmd run product-ownership:check`:通过,新建页与专用弹窗均自动清空其他归属。
|
||||||
|
- `npm.cmd run product-ownership-display:check`:通过,业务字段名、选择提示和详情空值文案配置完整。
|
||||||
|
- `npm.cmd run resource-pages:check`:通过,详情 46 类、新建 25 类、编辑 23 类。
|
||||||
|
- `npm.cmd run build`:通过,TypeScript 类型检查与 Vite 生产构建成功。
|
||||||
|
- `git diff --check`:通过,未发现空白符错误。
|
||||||
|
|
||||||
|
## 风险评估
|
||||||
|
|
||||||
|
修改仅作用于智能气阀新建表单的前端交互与文案,不改变数据库、接口结构及后端校验。
|
||||||
37
docs/项目文档_智能气阀新建归属互斥_v1.0.md
Normal file
37
docs/项目文档_智能气阀新建归属互斥_v1.0.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# 项目文档:智能气阀新建归属互斥 v1.0
|
||||||
|
|
||||||
|
## 1. 项目概述
|
||||||
|
|
||||||
|
智能气阀新建页面将库房、气站、配送点和用户作为同一组互斥归属。选择任一归属后自动清空其他归属,避免构造后端必然拒绝的多重归属请求。
|
||||||
|
|
||||||
|
## 2. 目录结构说明
|
||||||
|
|
||||||
|
```text
|
||||||
|
platforms/
|
||||||
|
├── frontend/platform_admin/
|
||||||
|
│ ├── src/api/resources.ts
|
||||||
|
│ ├── src/views/resource/ResourceRecordPage.vue
|
||||||
|
│ └── scripts/
|
||||||
|
│ ├── check-product-ownership-action.mjs
|
||||||
|
│ └── check-product-ownership-display.mjs
|
||||||
|
└── docs/
|
||||||
|
├── 项目文档_智能气阀新建归属互斥_v1.0.md
|
||||||
|
└── 操作日志_智能气阀新建归属互斥_20260813.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 核心行为
|
||||||
|
|
||||||
|
- 新建页字段标题使用业务名称,不显示“唯一标识”。
|
||||||
|
- 新建表单顶部说明四选一规则和自动清空行为。
|
||||||
|
- 未选择时显示“请选择…”;详情空值继续显示“未填写”。
|
||||||
|
- 选择一个非空归属时自动清空另外三个。
|
||||||
|
- 主动清空当前归属不会自动选择其他值。
|
||||||
|
- 关系变更后继续调用共享联动逻辑,避免影响其他资源表单。
|
||||||
|
|
||||||
|
## 4. 接口兼容性
|
||||||
|
|
||||||
|
创建请求仍使用 `warehouse_identity`、`gas_basic_identity`、`delivery_basic_identity` 和 `user_account_identity`。后端仍执行最多一个当前归属的最终校验。
|
||||||
|
|
||||||
|
## 5. 维护指南
|
||||||
|
|
||||||
|
新建页和“变更智能气阀归属”弹窗应保持相同的互斥规则。新增归属类型时必须同步更新两处字段集合、后端校验和专项检查。
|
||||||
@@ -10,6 +10,11 @@ const componentUrl = new URL(
|
|||||||
import.meta.url,
|
import.meta.url,
|
||||||
);
|
);
|
||||||
const source = await readFile(fileURLToPath(componentUrl), 'utf8');
|
const source = await readFile(fileURLToPath(componentUrl), 'utf8');
|
||||||
|
const recordPageUrl = new URL(
|
||||||
|
'../src/views/resource/ResourceRecordPage.vue',
|
||||||
|
import.meta.url,
|
||||||
|
);
|
||||||
|
const recordPageSource = await readFile(fileURLToPath(recordPageUrl), 'utf8');
|
||||||
const requiredFragments = [
|
const requiredFragments = [
|
||||||
'@change-relation="changeRelation"',
|
'@change-relation="changeRelation"',
|
||||||
'ownershipKeys.includes(field.key)',
|
'ownershipKeys.includes(field.key)',
|
||||||
@@ -24,4 +29,20 @@ for (const fragment of requiredFragments) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('智能气阀归属互斥检查通过:选择时自动清空,提交时保留防御校验。');
|
const createPageFragments = [
|
||||||
|
'@change-relation="changeRelation"',
|
||||||
|
"mode === 'create' && definition.name === 'product_info'",
|
||||||
|
'当前归属只能选择库房、气站、配送点或用户中的一个;选择新归属时会自动清空其他归属。',
|
||||||
|
"definition.value.name === 'product_info'",
|
||||||
|
'productOwnershipKeys.includes(field.key)',
|
||||||
|
'if (key !== field.key) form[key] = undefined',
|
||||||
|
'await relationLinkage.change(field, value)',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const fragment of createPageFragments) {
|
||||||
|
if (!recordPageSource.includes(fragment)) {
|
||||||
|
throw new Error(`智能气阀新建页归属互斥检查缺少实现:${fragment}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('智能气阀归属互斥检查通过:新建页与专用弹窗均自动清空,提交时保留防御校验。');
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import { fileURLToPath } from 'node:url';
|
|||||||
const resourcesUrl = new URL('../src/api/resources.ts', import.meta.url);
|
const resourcesUrl = new URL('../src/api/resources.ts', import.meta.url);
|
||||||
const source = await readFile(fileURLToPath(resourcesUrl), 'utf8');
|
const source = await readFile(fileURLToPath(resourcesUrl), 'utf8');
|
||||||
const ownershipRelations = [
|
const ownershipRelations = [
|
||||||
"relation('warehouse_identity', '/product_warehouse', false, { emptyText: '未填写' })",
|
"relation('warehouse_identity', '/product_warehouse', false, { label: '库房', emptyText: '未填写', placeholder: '请选择库房' })",
|
||||||
"relation('gas_basic_identity', '/gas_basic', false, { emptyText: '未填写' })",
|
"relation('gas_basic_identity', '/gas_basic', false, { label: '气站', emptyText: '未填写', placeholder: '请选择气站' })",
|
||||||
"relation('delivery_basic_identity', '/delivery_basic', false, { emptyText: '未填写' })",
|
"relation('delivery_basic_identity', '/delivery_basic', false, { label: '配送点', emptyText: '未填写', placeholder: '请选择配送点' })",
|
||||||
"relation('user_account_identity', '/user_account', false, { emptyText: '未填写' })",
|
"relation('user_account_identity', '/user_account', false, { label: '用户', emptyText: '未填写', placeholder: '请选择用户' })",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const relation of ownershipRelations) {
|
for (const relation of ownershipRelations) {
|
||||||
|
|||||||
@@ -447,7 +447,7 @@ export const resources: ResourceUiDefinition[] = [
|
|||||||
define('producer_account', '生产商管理', 'writable', [f('producer_code', { required: true }), f('name', { required: true }), f('credit_code'), f('principal'), f('phone'), f('address', { emptyText: '未填写', placeholder: '请输入地址' }), f('username', { required: true }), f('password', { required: true }), f('display_name'), producerAdminRole(), f('remark')]),
|
define('producer_account', '生产商管理', 'writable', [f('producer_code', { required: true }), f('name', { required: true }), f('credit_code'), f('principal'), f('phone'), f('address', { emptyText: '未填写', placeholder: '请输入地址' }), f('username', { required: true }), f('password', { required: true }), f('display_name'), producerAdminRole(), f('remark')]),
|
||||||
define('product_type', '智能气阀类型', 'editable', [f('code', { required: true }), f('name', { required: true })]),
|
define('product_type', '智能气阀类型', 'editable', [f('code', { required: true }), f('name', { required: true })]),
|
||||||
define('product_warehouse', '智能气阀库房', 'editable', [f('code', { required: true }), f('name', { required: true }), f('address', { emptyText: '未填写', placeholder: '请输入地址' }), f('manager'), f('phone')]),
|
define('product_warehouse', '智能气阀库房', 'editable', [f('code', { required: true }), f('name', { required: true }), f('address', { emptyText: '未填写', placeholder: '请输入地址' }), f('manager'), f('phone')]),
|
||||||
define('product_info', '智能气阀', 'editable', [f('code', { required: true }), f('name', { required: true }), relation('producer_account_identity', '/producer_account', true, { label: '生产商', listLabel: '生产商', listRelationNameOnly: true, displayRelationLabel: true, showIdentityCopy: true, emptyText: '未填写', placeholder: '请选择生产商' }), relation('product_type_identity', '/product_type', true, { label: '智能气阀类型', listLabel: '智能气阀类型', listRelationNameOnly: true, displayRelationLabel: true, showIdentityCopy: true, emptyText: '未填写', placeholder: '请选择智能气阀类型' }), f('params', { required: true }), relation('warehouse_identity', '/product_warehouse', false, { emptyText: '未填写' }), relation('gas_basic_identity', '/gas_basic', false, { emptyText: '未填写' }), relation('delivery_basic_identity', '/delivery_basic', false, { emptyText: '未填写' }), relation('user_account_identity', '/user_account', false, { emptyText: '未填写' }), f('produced_at', { required: true })], 'list', [
|
define('product_info', '智能气阀', 'editable', [f('code', { required: true }), f('name', { required: true }), relation('producer_account_identity', '/producer_account', true, { label: '生产商', listLabel: '生产商', listRelationNameOnly: true, displayRelationLabel: true, showIdentityCopy: true, emptyText: '未填写', placeholder: '请选择生产商' }), relation('product_type_identity', '/product_type', true, { label: '智能气阀类型', listLabel: '智能气阀类型', listRelationNameOnly: true, displayRelationLabel: true, showIdentityCopy: true, emptyText: '未填写', placeholder: '请选择智能气阀类型' }), f('params', { required: true }), relation('warehouse_identity', '/product_warehouse', false, { label: '库房', emptyText: '未填写', placeholder: '请选择库房' }), relation('gas_basic_identity', '/gas_basic', false, { label: '气站', emptyText: '未填写', placeholder: '请选择气站' }), relation('delivery_basic_identity', '/delivery_basic', false, { label: '配送点', emptyText: '未填写', placeholder: '请选择配送点' }), relation('user_account_identity', '/user_account', false, { label: '用户', emptyText: '未填写', placeholder: '请选择用户' }), f('produced_at', { required: true })], 'list', [
|
||||||
{ name: '修改智能气阀状态', resource: '/product_info/:identity/lifecycle', method: 'PATCH', fields: [f('product_status', { required: true, type: 'select', options: [{ label: '待处理', value: 10 }, { label: '在库', value: 28 }, { label: '运输中', value: 29 }, { label: '使用中', value: 30 }, { label: '维修中', value: 31 }, { label: '已报废', value: 27 }] })] },
|
{ name: '修改智能气阀状态', resource: '/product_info/:identity/lifecycle', method: 'PATCH', fields: [f('product_status', { required: true, type: 'select', options: [{ label: '待处理', value: 10 }, { label: '在库', value: 28 }, { label: '运输中', value: 29 }, { label: '使用中', value: 30 }, { label: '维修中', value: 31 }, { label: '已报废', value: 27 }] })] },
|
||||||
{ name: '变更智能气阀归属', resource: '/product_info/:identity', method: 'PUT', fields: [relation('warehouse_identity', '/product_warehouse'), relation('gas_basic_identity', '/gas_basic'), relation('delivery_basic_identity', '/delivery_basic'), relation('user_account_identity', '/user_account'), f('action', { required: true, type: 'select', options: [{ label: '入库', value: 'warehouse' }, { label: '分配', value: 'assigned' }, { label: '归还', value: 'returned' }, { label: '人工调整', value: 'manual' }] }), f('reason', { required: true }), f('remark')] },
|
{ name: '变更智能气阀归属', resource: '/product_info/:identity', method: 'PUT', fields: [relation('warehouse_identity', '/product_warehouse'), relation('gas_basic_identity', '/gas_basic'), relation('delivery_basic_identity', '/delivery_basic'), relation('user_account_identity', '/user_account'), f('action', { required: true, type: 'select', options: [{ label: '入库', value: 'warehouse' }, { label: '分配', value: 'assigned' }, { label: '归还', value: 'returned' }, { label: '人工调整', value: 'manual' }] }), f('reason', { required: true }), f('remark')] },
|
||||||
]),
|
]),
|
||||||
|
|||||||
@@ -116,6 +116,14 @@
|
|||||||
<div class="form-shell">基本信息</div>
|
<div class="form-shell">基本信息</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="form-shell form-content">
|
<div class="form-shell form-content">
|
||||||
|
<a-alert
|
||||||
|
v-if="mode === 'create' && definition.name === 'product_info'"
|
||||||
|
class="workflow-alert"
|
||||||
|
type="warning"
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
当前归属只能选择库房、气站、配送点或用户中的一个;选择新归属时会自动清空其他归属。
|
||||||
|
</a-alert>
|
||||||
<a-form :model="form" layout="vertical">
|
<a-form :model="form" layout="vertical">
|
||||||
<ResourceFieldForm
|
<ResourceFieldForm
|
||||||
v-model="form"
|
v-model="form"
|
||||||
@@ -126,7 +134,7 @@
|
|||||||
:relation-loading="relations.loading"
|
:relation-loading="relations.loading"
|
||||||
:role-options="roleOptions"
|
:role-options="roleOptions"
|
||||||
:contract-attachment="contractAttachmentState"
|
:contract-attachment="contractAttachmentState"
|
||||||
@change-relation="relationLinkage.change"
|
@change-relation="changeRelation"
|
||||||
@search-relation="relationLinkage.search"
|
@search-relation="relationLinkage.search"
|
||||||
@select-attachment="contractAttachment.select"
|
@select-attachment="contractAttachment.select"
|
||||||
@remove-attachment="confirmRemoveContractAttachment"
|
@remove-attachment="confirmRemoveContractAttachment"
|
||||||
@@ -188,7 +196,11 @@ import {
|
|||||||
recordStatusColor,
|
recordStatusColor,
|
||||||
recordStatusLabel,
|
recordStatusLabel,
|
||||||
} from '@/api/resource-display';
|
} from '@/api/resource-display';
|
||||||
import { getResource, type DetailAction } from '@/api/resources';
|
import {
|
||||||
|
getResource,
|
||||||
|
type DetailAction,
|
||||||
|
type ResourceField,
|
||||||
|
} from '@/api/resources';
|
||||||
import { useUserStore } from '@/store';
|
import { useUserStore } from '@/store';
|
||||||
import ResourceAccountSummary from './ResourceAccountSummary.vue';
|
import ResourceAccountSummary from './ResourceAccountSummary.vue';
|
||||||
import ResourceActionDialog from './ResourceActionDialog.vue';
|
import ResourceActionDialog from './ResourceActionDialog.vue';
|
||||||
@@ -246,6 +258,12 @@ const staffOwnerGuard = useStaffCredentialOwnerGuard({
|
|||||||
const resolvedStaffType = staffOwnerGuard.resolvedStaffType;
|
const resolvedStaffType = staffOwnerGuard.resolvedStaffType;
|
||||||
const relations = staffOwnerGuard.relations;
|
const relations = staffOwnerGuard.relations;
|
||||||
const relationLinkage = useResourceRelationLinkage(form, relations);
|
const relationLinkage = useResourceRelationLinkage(form, relations);
|
||||||
|
const productOwnershipKeys = [
|
||||||
|
'warehouse_identity',
|
||||||
|
'gas_basic_identity',
|
||||||
|
'delivery_basic_identity',
|
||||||
|
'user_account_identity',
|
||||||
|
];
|
||||||
const actionVisible = ref(false);
|
const actionVisible = ref(false);
|
||||||
const activeAction = ref<DetailAction>();
|
const activeAction = ref<DetailAction>();
|
||||||
const avatar = useResourceAvatar();
|
const avatar = useResourceAvatar();
|
||||||
@@ -262,6 +280,22 @@ const contractAttachmentState = computed(() => ({
|
|||||||
removed: contractAttachment.removed.value,
|
removed: contractAttachment.removed.value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
/** 新建智能气阀选择归属时清空其他互斥归属,再执行字段原有联动。 */
|
||||||
|
async function changeRelation(field: ResourceField, value: unknown) {
|
||||||
|
if (
|
||||||
|
mode.value === 'create' &&
|
||||||
|
definition.value.name === 'product_info' &&
|
||||||
|
productOwnershipKeys.includes(field.key) &&
|
||||||
|
value != null &&
|
||||||
|
String(value).trim() !== ''
|
||||||
|
) {
|
||||||
|
for (const key of productOwnershipKeys) {
|
||||||
|
if (key !== field.key) form[key] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await relationLinkage.change(field, value);
|
||||||
|
}
|
||||||
|
|
||||||
const accountSummary = computed(() => usesAccountSummary(definition.value));
|
const accountSummary = computed(() => usesAccountSummary(definition.value));
|
||||||
const accountSummaryVisible = computed(
|
const accountSummaryVisible = computed(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
Reference in New Issue
Block a user