fix: 修复 Vben 三端业务行为不一致问题

- 修复 MES 多个选择弹窗跨页多选时 rowId 判断反向的问题
- 修复库存盘点条件值组件对 undefined/null 的处理不一致问题
- 修复 IoT 告警配置模板字段清空时未等待表单更新的问题
- 对齐 antd、antdv-next 的 BPM 流程详情状态枚举和打印组件命名
- 清理 ele BPM 表单面板中未使用的字段编辑死代码
- 补全 ele 用户选择弹窗左右分页大小切换和搜索同步逻辑
This commit is contained in:
YunaiV
2026-06-24 23:22:13 -07:00
parent 2dd76ea7dd
commit c9a7a9b8f6
11 changed files with 93 additions and 228 deletions

View File

@@ -9,7 +9,7 @@ import {
BpmFieldPermissionType,
BpmModelFormType,
BpmModelType,
BpmProcessInstanceStatus,
BpmTaskStatusEnum,
DICT_TYPE,
} from '@vben/constants';
import {
@@ -34,7 +34,7 @@ import { registerComponent } from '#/utils';
import ProcessInstanceBpmnViewer from './modules/bpm-viewer.vue';
import ProcessInstanceOperationButton from './modules/operation-button.vue';
import ProcessssPrint from './modules/process-print.vue';
import ProcessPrint from './modules/process-print.vue';
import ProcessInstanceSimpleViewer from './modules/simple-bpm-viewer.vue';
import BpmProcessInstanceTaskList from './modules/task-list.vue';
import ProcessInstanceTimeline from './modules/time-line.vue';
@@ -61,10 +61,13 @@ const auditIconsMap: {
| typeof SvgBpmRejectIcon
| typeof SvgBpmRunningIcon;
} = {
[BpmProcessInstanceStatus.RUNNING]: SvgBpmRunningIcon,
[BpmProcessInstanceStatus.APPROVE]: SvgBpmApproveIcon,
[BpmProcessInstanceStatus.REJECT]: SvgBpmRejectIcon,
[BpmProcessInstanceStatus.CANCEL]: SvgBpmCancelIcon,
[BpmTaskStatusEnum.RUNNING]: SvgBpmRunningIcon,
[BpmTaskStatusEnum.APPROVE]: SvgBpmApproveIcon,
[BpmTaskStatusEnum.REJECT]: SvgBpmRejectIcon,
[BpmTaskStatusEnum.CANCEL]: SvgBpmCancelIcon,
[BpmTaskStatusEnum.APPROVING]: SvgBpmApproveIcon,
[BpmTaskStatusEnum.RETURN]: SvgBpmRejectIcon,
[BpmTaskStatusEnum.WAIT]: SvgBpmRunningIcon,
};
const activityNodes = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>([]); // 审批节点信息
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
@@ -189,7 +192,7 @@ const refresh = () => {
};
const [PrintModal, printModalApi] = useVbenModal({
connectedComponent: ProcessssPrint,
connectedComponent: ProcessPrint,
destroyOnClose: true,
});

View File

@@ -41,8 +41,8 @@ function getMultipleSelectedRows() {
] as MesMdClientApi.Client[];
records.forEach((row) => {
const rowId = row.id;
if (!rowId) {
selectedMap.set(rowId as number, row);
if (rowId !== undefined && rowId !== null) {
selectedMap.set(rowId, row);
}
});
return [...selectedMap.values()];

View File

@@ -42,8 +42,8 @@ function getMultipleSelectedRows() {
] as MesMdItemApi.Item[];
records.forEach((row) => {
const rowId = row.id;
if (!rowId) {
selectedMap.set(rowId as number, row);
if (rowId !== undefined && rowId !== null) {
selectedMap.set(rowId, row);
}
});
return [...selectedMap.values()];

View File

@@ -41,8 +41,8 @@ function getMultipleSelectedRows() {
] as MesMdVendorApi.Vendor[];
records.forEach((row) => {
const rowId = row.id;
if (!rowId) {
selectedMap.set(rowId as number, row);
if (rowId !== undefined && rowId !== null) {
selectedMap.set(rowId, row);
}
});
return [...selectedMap.values()];

View File

@@ -36,8 +36,8 @@ function getMultipleSelectedRows() {
] as MesTmToolApi.Tool[];
records.forEach((row) => {
const rowId = row.id;
if (!rowId) {
selectedMap.set(rowId as number, row);
if (rowId !== undefined && rowId !== null) {
selectedMap.set(rowId, row);
}
});
return [...selectedMap.values()];

View File

@@ -77,7 +77,7 @@ function handleQualityStatusChange(value?: number) {
emit('update:modelValue', undefined);
emit('valueChange', {
valueId: undefined,
valueCode: value === null ? '' : String(value),
valueCode: value === undefined || value === null ? '' : String(value),
valueName: selected?.label || '',
});
}
@@ -109,14 +109,15 @@ async function loadCascadeData() {
: undefined;
return;
}
if (!props.modelValue) {
const valueId = props.modelValue;
if (valueId === undefined || valueId === null) {
return;
}
if (props.type === MesWmStockTakingParamTypeEnum.LOCATION) {
const location = await getWarehouseLocation(props.modelValue);
const location = await getWarehouseLocation(valueId);
locationWarehouseId.value = location?.warehouseId;
} else if (props.type === MesWmStockTakingParamTypeEnum.AREA) {
const area = await getWarehouseArea(props.modelValue);
const area = await getWarehouseArea(valueId);
areaWarehouseId.value = area?.warehouseId;
areaLocationId.value = area?.locationId;
}

View File

@@ -9,7 +9,7 @@ import {
BpmFieldPermissionType,
BpmModelFormType,
BpmModelType,
BpmProcessInstanceStatus,
BpmTaskStatusEnum,
DICT_TYPE,
} from '@vben/constants';
import {
@@ -34,7 +34,7 @@ import { registerComponent } from '#/utils';
import ProcessInstanceBpmnViewer from './modules/bpm-viewer.vue';
import ProcessInstanceOperationButton from './modules/operation-button.vue';
import ProcessssPrint from './modules/process-print.vue';
import ProcessPrint from './modules/process-print.vue';
import ProcessInstanceSimpleViewer from './modules/simple-bpm-viewer.vue';
import BpmProcessInstanceTaskList from './modules/task-list.vue';
import ProcessInstanceTimeline from './modules/time-line.vue';
@@ -61,10 +61,13 @@ const auditIconsMap: {
| typeof SvgBpmRejectIcon
| typeof SvgBpmRunningIcon;
} = {
[BpmProcessInstanceStatus.RUNNING]: SvgBpmRunningIcon,
[BpmProcessInstanceStatus.APPROVE]: SvgBpmApproveIcon,
[BpmProcessInstanceStatus.REJECT]: SvgBpmRejectIcon,
[BpmProcessInstanceStatus.CANCEL]: SvgBpmCancelIcon,
[BpmTaskStatusEnum.RUNNING]: SvgBpmRunningIcon,
[BpmTaskStatusEnum.APPROVE]: SvgBpmApproveIcon,
[BpmTaskStatusEnum.REJECT]: SvgBpmRejectIcon,
[BpmTaskStatusEnum.CANCEL]: SvgBpmCancelIcon,
[BpmTaskStatusEnum.APPROVING]: SvgBpmApproveIcon,
[BpmTaskStatusEnum.RETURN]: SvgBpmRejectIcon,
[BpmTaskStatusEnum.WAIT]: SvgBpmRunningIcon,
};
const activityNodes = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>([]); // 审批节点信息
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
@@ -189,7 +192,7 @@ const refresh = () => {
};
const [PrintModal, printModalApi] = useVbenModal({
connectedComponent: ProcessssPrint,
connectedComponent: ProcessPrint,
destroyOnClose: true,
});

View File

@@ -1,4 +1,3 @@
<!-- eslint-disable no-unused-vars -->
<script lang="ts" setup>
import { computed, inject, nextTick, onMounted, ref, toRaw, watch } from 'vue';
@@ -24,26 +23,7 @@ const prefix = inject('prefix');
const formKey = ref<number | string | undefined>(undefined);
const businessKey = ref('');
const optionModelTitle = ref('');
const fieldList = ref<any[]>([]);
const formFieldForm = ref<any>({});
const fieldType = ref({
long: '长整型',
string: '字符串',
boolean: '布尔类',
date: '日期类',
enum: '枚举类',
custom: '自定义类型',
});
const formFieldIndex = ref(-1); // 编辑中的字段, -1 为新增
const formFieldOptionIndex = ref(-1); // 编辑中的字段配置项, -1 为新增
const fieldModelVisible = ref(false);
const fieldOptionModelVisible = ref(false);
const fieldOptionForm = ref<any>({}); // 当前激活的字段配置项数据
const fieldOptionType = ref(''); // 当前激活的字段配置项弹窗 类型
const fieldEnumList = ref<any[]>([]); // 枚举值列表
const fieldConstraintsList = ref<any[]>([]); // 约束条件列表
const fieldPropertiesList = ref<any[]>([]); // 绑定属性列表
const bpmnELement = ref();
const elExtensionElements = ref();
const formData = ref();
@@ -94,180 +74,6 @@ const _updateElementBusinessKey = () => {
},
);
};
// 根据类型调整字段type
// @ts-expect-error unused
const _changeFieldTypeType = (type: any) => {
formFieldForm.value.type = type === 'custom' ? '' : type;
};
// 打开字段详情侧边栏
// @ts-expect-error unused
const _openFieldForm = (field: any, index: any) => {
formFieldIndex.value = index;
if (index === -1) {
formFieldForm.value = {};
// 初始化枚举值列表
fieldEnumList.value = [];
// 初始化约束条件列表
fieldConstraintsList.value = [];
// 初始化自定义属性列表
fieldPropertiesList.value = [];
} else {
const FieldObject = formData.value.fields[index];
formFieldForm.value = cloneDeep(field);
// 设置自定义类型
// this.$set(this.formFieldForm, "typeType", !this.fieldType[field.type] ? "custom" : field.type);
formFieldForm.value.typeType = fieldType.value[
field.type as keyof typeof fieldType.value
]
? field.type
: 'custom';
// 初始化枚举值列表
field.type === 'enum' &&
(fieldEnumList.value = cloneDeep(FieldObject?.values || []));
// 初始化约束条件列表
fieldConstraintsList.value = cloneDeep(
FieldObject?.validation?.constraints || [],
);
// 初始化自定义属性列表
fieldPropertiesList.value = cloneDeep(
FieldObject?.properties?.values || [],
);
}
fieldModelVisible.value = true;
};
// 打开字段 某个 配置项 弹窗
// @ts-expect-error unused
const _openFieldOptionForm = (option: any, index: any, type: any) => {
fieldOptionModelVisible.value = true;
fieldOptionType.value = type;
formFieldOptionIndex.value = index;
if (type === 'property') {
fieldOptionForm.value = option ? cloneDeep(option) : {};
return (optionModelTitle.value = '属性配置');
}
if (type === 'enum') {
fieldOptionForm.value = option ? cloneDeep(option) : {};
return (optionModelTitle.value = '枚举值配置');
}
fieldOptionForm.value = option ? cloneDeep(option) : {};
return (optionModelTitle.value = '约束条件配置');
};
// 保存字段 某个 配置项
// @ts-expect-error unused
const _saveFieldOption = () => {
if (formFieldOptionIndex.value === -1) {
if (fieldOptionType.value === 'property') {
fieldPropertiesList.value.push(fieldOptionForm.value);
}
if (fieldOptionType.value === 'constraint') {
fieldConstraintsList.value.push(fieldOptionForm.value);
}
if (fieldOptionType.value === 'enum') {
fieldEnumList.value.push(fieldOptionForm.value);
}
} else {
fieldOptionType.value === 'property' &&
fieldPropertiesList.value.splice(
formFieldOptionIndex.value,
1,
fieldOptionForm.value,
);
fieldOptionType.value === 'constraint' &&
fieldConstraintsList.value.splice(
formFieldOptionIndex.value,
1,
fieldOptionForm.value,
);
fieldOptionType.value === 'enum' &&
fieldEnumList.value.splice(
formFieldOptionIndex.value,
1,
fieldOptionForm.value,
);
}
fieldOptionModelVisible.value = false;
fieldOptionForm.value = {};
};
// 保存字段配置
// @ts-expect-error unused
const _saveField = () => {
const { id, type, label, defaultValue, datePattern } = formFieldForm.value;
const Field = bpmnInstances().moddle.create(`${prefix}:FormField`, {
id,
type,
label,
});
defaultValue && (Field.defaultValue = defaultValue);
datePattern && (Field.datePattern = datePattern);
// 构建属性
if (fieldPropertiesList.value && fieldPropertiesList.value.length > 0) {
const fieldPropertyList = fieldPropertiesList.value.map((fp: any) => {
return bpmnInstances().moddle.create(`${prefix}:Property`, {
id: fp.id,
value: fp.value,
});
});
Field.properties = bpmnInstances().moddle.create(`${prefix}:Properties`, {
values: fieldPropertyList,
});
}
// 构建校验规则
if (fieldConstraintsList.value && fieldConstraintsList.value.length > 0) {
const fieldConstraintList = fieldConstraintsList.value.map((fc: any) => {
return bpmnInstances().moddle.create(`${prefix}:Constraint`, {
name: fc.name,
config: fc.config,
});
});
Field.validation = bpmnInstances().moddle.create(`${prefix}:Validation`, {
constraints: fieldConstraintList,
});
}
// 构建枚举值
if (fieldEnumList.value && fieldEnumList.value.length > 0) {
Field.values = fieldEnumList.value.map((fe: any) => {
return bpmnInstances().moddle.create(`${prefix}:Value`, {
name: fe.name,
id: fe.id,
});
});
}
// 更新数组 与 表单配置实例
if (formFieldIndex.value === -1) {
fieldList.value.push(formFieldForm.value);
formData.value.fields.push(Field);
} else {
fieldList.value.splice(formFieldIndex.value, 1, formFieldForm.value);
formData.value.fields.splice(formFieldIndex.value, 1, Field);
}
updateElementExtensions();
fieldModelVisible.value = false;
};
// 移除某个 字段的 配置项
// @ts-expect-error unused
const _removeFieldOptionItem = (_option: any, index: any, type: any) => {
// console.log(option, 'option')
if (type === 'property') {
fieldPropertiesList.value.splice(index, 1);
return;
}
if (type === 'enum') {
fieldEnumList.value.splice(index, 1);
return;
}
fieldConstraintsList.value.splice(index, 1);
};
// 移除 字段
// @ts-expect-error unused
const _removeField = (field: any, index: any) => {
console.warn(field, 'field');
fieldList.value.splice(index, 1);
formData.value.fields.splice(index, 1);
updateElementExtensions();
};
const updateElementExtensions = () => {
// 更新回扩展元素

View File

@@ -125,7 +125,7 @@ export function useFormSchema(): VbenFormSchema[] {
!hasReceiveType(values, IotAlertReceiveTypeEnum.SMS) &&
values.smsTemplateCode
) {
formApi.setFieldValue('smsTemplateCode', undefined);
await formApi.setFieldValue('smsTemplateCode', undefined);
}
},
},
@@ -143,7 +143,7 @@ export function useFormSchema(): VbenFormSchema[] {
!hasReceiveType(values, IotAlertReceiveTypeEnum.MAIL) &&
values.mailTemplateCode
) {
formApi.setFieldValue('mailTemplateCode', undefined);
await formApi.setFieldValue('mailTemplateCode', undefined);
}
},
},
@@ -162,7 +162,7 @@ export function useFormSchema(): VbenFormSchema[] {
!hasReceiveType(values, IotAlertReceiveTypeEnum.NOTIFY) &&
values.notifyTemplateCode
) {
formApi.setFieldValue('notifyTemplateCode', undefined);
await formApi.setFieldValue('notifyTemplateCode', undefined);
}
},
},

View File

@@ -77,7 +77,7 @@ function handleQualityStatusChange(value?: number) {
emit('update:modelValue', undefined);
emit('valueChange', {
valueId: undefined,
valueCode: value === null ? '' : String(value),
valueCode: value === undefined || value === null ? '' : String(value),
valueName: selected?.label || '',
});
}
@@ -109,14 +109,15 @@ async function loadCascadeData() {
: undefined;
return;
}
if (props.modelValue === null) {
const valueId = props.modelValue;
if (valueId === undefined || valueId === null) {
return;
}
if (props.type === MesWmStockTakingParamTypeEnum.LOCATION) {
const location = await getWarehouseLocation(props.modelValue as number);
const location = await getWarehouseLocation(valueId);
locationWarehouseId.value = location?.warehouseId;
} else if (props.type === MesWmStockTakingParamTypeEnum.AREA) {
const area = await getWarehouseArea(props.modelValue as number);
const area = await getWarehouseArea(valueId);
areaWarehouseId.value = area?.warehouseId;
areaLocationId.value = area?.locationId;
}

View File

@@ -2,7 +2,7 @@
import type { SystemDeptApi } from '#/api/system/dept';
import type { SystemUserApi } from '#/api/system/user';
import { computed, ref } from 'vue';
import { computed, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { handleTree } from '@vben/utils';
@@ -65,6 +65,7 @@ const deptSearchKeys = ref('');
// 用户数据管理
const userList = ref<SystemUserApi.User[]>([]); // 存储所有已知用户
const selectedUserIds = ref<number[]>([]);
const transferRef = ref<any>();
const [Modal, modalApi] = useVbenModal({
onCancel: handleCancel,
@@ -272,12 +273,24 @@ async function handleLeftPaginationChange(page: number) {
await loadUserData(page, leftListState.value.pagination.pageSize);
}
/** 处理左侧每页条数变化 */
async function handleLeftPageSizeChange(pageSize: number) {
await loadUserData(1, pageSize);
}
/** 处理右侧分页变化 */
function handleRightPaginationChange(page: number) {
rightListState.value.pagination.current = page;
updateRightListData();
}
/** 处理右侧每页条数变化 */
function handleRightPageSizeChange(pageSize: number) {
rightListState.value.pagination.current = 1;
rightListState.value.pagination.pageSize = pageSize;
updateRightListData();
}
/** 处理用户选择变化 */
function handleUserChange(
value: (number | string)[],
@@ -290,6 +303,41 @@ function handleUserChange(
updateRightListData();
}
async function handleLeftUserSearch(query: string) {
leftListState.value.searchValue = query;
leftListState.value.pagination.current = 1;
await loadUserData(1, leftListState.value.pagination.pageSize);
}
function handleRightUserSearch(query: string) {
rightListState.value.searchValue = query;
rightListState.value.pagination.current = 1;
updateRightListData();
}
function getTransferPanelQuery(panel: any) {
const panelInstance = panel?.value ?? panel;
return panelInstance?.query?.value ?? panelInstance?.query ?? '';
}
watch(
() => getTransferPanelQuery(transferRef.value?.leftPanel),
(query) => {
if (query !== leftListState.value.searchValue) {
void handleLeftUserSearch(query);
}
},
);
watch(
() => getTransferPanelQuery(transferRef.value?.rightPanel),
(query) => {
if (query !== rightListState.value.searchValue) {
handleRightUserSearch(query);
}
},
);
/** 重置数据 */
function resetData() {
userList.value = [];
@@ -430,6 +478,7 @@ function processDeptNode(node: any): DeptTreeNode {
</ElCol>
<ElCol :span="18">
<ElTransfer
ref="transferRef"
v-model="selectedUserIds"
:data="transferDataSource"
:titles="['未选', '已选']"
@@ -450,6 +499,7 @@ function processDeptNode(node: any): DeptTreeNode {
layout="total, sizes, prev, pager, next"
small
@current-change="handleLeftPaginationChange"
@size-change="handleLeftPageSizeChange"
/>
<ElPagination
v-model:current-page="rightListState.pagination.current"
@@ -459,6 +509,7 @@ function processDeptNode(node: any): DeptTreeNode {
layout="total, sizes, prev, pager, next"
small
@current-change="handleRightPaginationChange"
@size-change="handleRightPageSizeChange"
/>
</div>
</ElCol>