feat: 分类顺序校验
This commit is contained in:
@@ -162,6 +162,7 @@
|
||||
:nodeType="modalNodeType"
|
||||
:detail="modalDetail"
|
||||
:belongProject="belongProject"
|
||||
:parentCategoryPath="parentNodeCategoryPath"
|
||||
@confirm="onNodeDetailConfirmFun"
|
||||
/>
|
||||
<!-- <userSelectTable
|
||||
@@ -178,7 +179,7 @@ import { ref, onMounted, nextTick, computed } from 'vue';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
import TreeCaseTable from '@/components/common/treeCaseTable/treeTable.vue';
|
||||
import { getTagKeyMap, NODE_TYPE } from '@/utils/enum/node';
|
||||
import { canAddChild } from '@/utils/node';
|
||||
import { canAddChild, findNodePath, extractCategoryNodeTypes } from '@/utils/node';
|
||||
import nodeDetailDialog from './nodeDetailDialog.vue';
|
||||
import { TASK_CALCULATE_STATUS, TASK_PROCESS_STATUS } from '@/utils/enum/task';
|
||||
import { disposeTagKey } from '@/views/task/projectDetail/components/project';
|
||||
@@ -193,6 +194,7 @@ const formItemList = ref<any>([]);
|
||||
const modalVisible = ref<boolean>(false);
|
||||
const formData = ref<any>({});
|
||||
const addNodeDisabled = ref<any>({});
|
||||
const parentNodeCategoryPath = ref<string[]>([]);
|
||||
|
||||
interface Props {
|
||||
data: any;
|
||||
@@ -267,9 +269,21 @@ const operationType = ref<OPERATION_TYPE>(OPERATION_TYPE.ADD);
|
||||
const modalNodeType = ref<string>('');
|
||||
const modalDetail = ref<any>(null);
|
||||
const addRowFun = (nodeType?: any) => {
|
||||
const checkRowData = getVxeRef()?.getCheckboxRecords(true);
|
||||
|
||||
let categoryPath: string[] = [];
|
||||
if (checkRowData && checkRowData.length > 0) {
|
||||
const { fullData } = getVxeRef().getTableData();
|
||||
const nodePath = findNodePath(fullData, checkRowData[0]);
|
||||
if (nodePath) {
|
||||
categoryPath = extractCategoryNodeTypes(nodePath);
|
||||
}
|
||||
}
|
||||
|
||||
operationType.value = OPERATION_TYPE.ADD;
|
||||
modalNodeType.value = nodeType;
|
||||
modalDetail.value = null;
|
||||
parentNodeCategoryPath.value = categoryPath;
|
||||
modalVisible.value = true;
|
||||
};
|
||||
const editRowFun = (row: any) => {
|
||||
|
||||
@@ -39,16 +39,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick, watch } from 'vue';
|
||||
import { ref, computed, nextTick, watch, onMounted } from 'vue';
|
||||
import Dialog from '@/components/common/dialog/index.vue';
|
||||
import TableForm from '@/components/common/table/tableForm.vue';
|
||||
import { getTagKeyMap, NODE_TYPE } from '@/utils/enum/node';
|
||||
import { getMemberListIds } from '@/utils/task';
|
||||
import { disposeTagKey } from '@/views/task/projectDetail/components/project';
|
||||
import { isCategoryType, isCategoryNodeType } from '@/utils/node';
|
||||
import { isCategoryType, isCategoryNodeType, validateCategoryLevel } from '@/utils/node';
|
||||
import flowTemplateSelect from './flowTemplateSelect.vue';
|
||||
import knowledgeSelect from './knowledgeSelect.vue';
|
||||
import { TABLE_NAME } from '@/utils/enum/tableName';
|
||||
import { useDict } from '@/utils/useDict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
enum OPERATION_TYPE {
|
||||
ADD = 'add',
|
||||
@@ -62,6 +64,7 @@ interface Props {
|
||||
nodeType?: string;
|
||||
modalTableNameList?: string[];
|
||||
belongProject?: boolean;
|
||||
parentCategoryPath?: string[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -72,6 +75,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
modalTableNameList: () => ([TABLE_NAME.TASK_POOL_CATEGORY, TABLE_NAME.TASK_POOL_TASK, TABLE_NAME.TASK_POOL_PERFORMANCE]),
|
||||
detail: null,
|
||||
belongProject: false,
|
||||
parentCategoryPath: () => [],
|
||||
});
|
||||
|
||||
const emits = defineEmits(['update:modelValue', 'confirm']);
|
||||
@@ -80,6 +84,7 @@ const visible = computed({
|
||||
set: (val) => emits('update:modelValue', val),
|
||||
});
|
||||
const tableFormRef = ref<any>();
|
||||
const { t } = useI18n();
|
||||
|
||||
const localDiaTitle = ref('');
|
||||
const localDetail = ref<any>(null);
|
||||
@@ -88,6 +93,8 @@ const localTableName = ref(props.tableName || '');
|
||||
// 流程模板相关
|
||||
const selectedFlowTemplate = ref<any>(null);
|
||||
const standard = ref();
|
||||
const tagSortOrderList = ref<string[]>([]);
|
||||
const tagNameMap = ref<Map<string, string>>(new Map());
|
||||
const onShowFun = () => {
|
||||
resetFun();
|
||||
nextTick(() => {
|
||||
@@ -154,28 +161,28 @@ const prepareFromProps = () => {
|
||||
const operation = props.operationType;
|
||||
const detail = props.detail;
|
||||
|
||||
let text = operation === OPERATION_TYPE.ADD ? '新增' : '编辑';
|
||||
let text = operation === OPERATION_TYPE.ADD ? t('通用.新增') : t('通用.编辑');
|
||||
let targetTableName = props.modalTableNameList?.[1] || '';
|
||||
switch (nodeType) {
|
||||
case NODE_TYPE.CATEGORY:
|
||||
text += '分类';
|
||||
text += t('工况库.分类');
|
||||
targetTableName = props.modalTableNameList?.[0] || targetTableName;
|
||||
break;
|
||||
case NODE_TYPE.TASK:
|
||||
if (props.belongProject) {
|
||||
text += '任务';
|
||||
text += t('通用.任务');
|
||||
} else {
|
||||
text += '工况';
|
||||
text += t('工况库.工况');
|
||||
}
|
||||
targetTableName = props.modalTableNameList?.[1] || targetTableName;
|
||||
break;
|
||||
case NODE_TYPE.PERFORMANCE:
|
||||
text += '指标';
|
||||
text += t('工况库.指标');
|
||||
targetTableName = props.modalTableNameList?.[2] || targetTableName;
|
||||
break;
|
||||
}
|
||||
if (isCategoryNodeType(nodeType)) {
|
||||
text += '分类';
|
||||
text += t('工况库.分类');
|
||||
targetTableName = props.modalTableNameList?.[0] || targetTableName;
|
||||
}
|
||||
|
||||
@@ -202,10 +209,48 @@ watch(() => props.modelValue, (val) => {
|
||||
}
|
||||
});
|
||||
|
||||
const ruleData = ref<any>({
|
||||
const ruleData = computed(() => ({
|
||||
nodeType: [
|
||||
{
|
||||
message: computed(() => {
|
||||
if (tagSortOrderList.value.length === 0) {
|
||||
return t('工况库.需按照配置顺序逐层添加');
|
||||
}
|
||||
const orderNames = tagSortOrderList.value
|
||||
.filter(type => isCategoryNodeType(type))
|
||||
.map(type => tagNameMap.value.get(type) || type)
|
||||
.join(' → ');
|
||||
return t('工况库.请按照顺序添加', { order: orderNames });
|
||||
}),
|
||||
trigger: 'change',
|
||||
validator: (val: unknown) => {
|
||||
if (localOperationType.value === OPERATION_TYPE.EDIT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isCategoryNodeType(val as string)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!props.parentCategoryPath || props.parentCategoryPath.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tagSortOrderList.value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return validateCategoryLevel(
|
||||
val as string,
|
||||
props.parentCategoryPath,
|
||||
tagSortOrderList.value
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
highValue: [
|
||||
{
|
||||
message: '指标类型为数值时,目标值应为数字',
|
||||
message: t('工况库.指标类型为数值时目标值应为数字'),
|
||||
trigger: 'change',
|
||||
validator: (val: unknown) => {
|
||||
if (val === null || val === undefined || val === '') {
|
||||
@@ -227,6 +272,22 @@ const ruleData = ref<any>({
|
||||
},
|
||||
|
||||
],
|
||||
}));
|
||||
|
||||
const initDictData = () => {
|
||||
const { TAG_TYPE_MAP_LIST, POOL_CATEGORY_TYPE } = useDict('TAG_TYPE_MAP_LIST', 'POOL_CATEGORY_TYPE');
|
||||
tagSortOrderList.value = TAG_TYPE_MAP_LIST?.value?.A?.reduce((acc: string[], curr: { value: string; }) => {
|
||||
acc.push(curr.value);
|
||||
return acc;
|
||||
}, []) || [];
|
||||
const categoryTypeList = POOL_CATEGORY_TYPE?.value.A || [];
|
||||
categoryTypeList.forEach((item: { value: string; label: string; }) => {
|
||||
tagNameMap.value.set(item.value, item.label);
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initDictData();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -19,6 +19,7 @@ const lang = {
|
||||
'下一步': 'Next',
|
||||
'查看': 'View',
|
||||
'审批类型': 'Approval Type',
|
||||
'任务': 'Task',
|
||||
},
|
||||
'菜单': {
|
||||
'首页': 'Home',
|
||||
@@ -89,6 +90,9 @@ const lang = {
|
||||
'节点': 'Node',
|
||||
'工况': 'Task',
|
||||
'指标': 'Performance',
|
||||
'需按照配置顺序逐层添加': 'Please add in configured order',
|
||||
'请按照顺序添加': 'Please add in order ({order})',
|
||||
'指标类型为数值时目标值应为数字': 'Target value must be a number when indicator type is numeric',
|
||||
'删除': 'Delete',
|
||||
'创建清单库': 'Create Pool',
|
||||
'导入Excel': 'Import Excel',
|
||||
|
||||
@@ -19,6 +19,7 @@ const lang = {
|
||||
'下一步': '下一步',
|
||||
'查看': '查看',
|
||||
'审批类型': '审批类型',
|
||||
'任务': '任务',
|
||||
},
|
||||
'菜单': {
|
||||
'首页': '首页',
|
||||
@@ -89,6 +90,9 @@ const lang = {
|
||||
'节点': '节点',
|
||||
'工况': '工况',
|
||||
'指标': '指标',
|
||||
'需按照配置顺序逐层添加': '需按照配置顺序逐层添加',
|
||||
'请按照顺序添加': '请按照顺序({order})添加',
|
||||
'指标类型为数值时目标值应为数字': '指标类型为数值时,目标值应为数字',
|
||||
'删除': '删除',
|
||||
'创建清单库': '创建清单库',
|
||||
'导入Excel': '导入Excel',
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { nodeTypeList, NODE_TYPE, LEVEL_TYPE, nodeTypeRelations, poolNodePropPickMap, poolNodeExtraPropPickMap } from '@/utils/enum/node';
|
||||
import type { TreeNode } from '@/utils/enum/node';
|
||||
import { getMemberListIds } from './task';
|
||||
import { CommonStore } from '@/stores/common';
|
||||
|
||||
const commonStore = CommonStore();
|
||||
export const toMap = <T extends string | number, U>(list: { value: T, label: U }[]): Record<T, U> => {
|
||||
return (list.reduce((obj, { value, label }) => {
|
||||
obj[value] = label;
|
||||
@@ -50,7 +52,8 @@ export const isCategoryType = (nodeType: string): boolean => {
|
||||
return nodeType === NODE_TYPE.CATEGORY;
|
||||
};
|
||||
export const isCategoryNodeType = (nodeType: string): boolean => {
|
||||
return !isInnerNodeType(nodeType);
|
||||
const categoryTypeList = (commonStore.getDictData('POOL_CATEGORY_TYPE'))?.A || [];
|
||||
return categoryTypeList.some((item: { value: string }) => item.value === nodeType);
|
||||
};
|
||||
export const isMixedNodeType = (nodeType: string): boolean => {
|
||||
return nodeType === NODE_TYPE.MIXED;
|
||||
@@ -320,3 +323,53 @@ export const extractLeafNodesWithParentTypes = (list: any[] | undefined): any[]
|
||||
walk(list, []);
|
||||
return result;
|
||||
};
|
||||
|
||||
export const findNodePath = (tree: TreeNode[], targetNode: TreeNode): TreeNode[] | null => {
|
||||
const dfs = (nodes: TreeNode[], path: TreeNode[]): TreeNode[] | null => {
|
||||
for (const node of nodes) {
|
||||
const currentPath = [...path, node];
|
||||
|
||||
if (node.fakeId === targetNode.fakeId ) {
|
||||
return currentPath;
|
||||
}
|
||||
|
||||
if (node.children && node.children.length > 0) {
|
||||
const result = dfs(node.children, currentPath);
|
||||
if (result) return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return dfs(tree, []);
|
||||
};
|
||||
|
||||
export const extractCategoryNodeTypes = (nodePath: TreeNode[]): string[] => {
|
||||
return nodePath
|
||||
.filter(node => isCategoryNodeType(node.nodeType))
|
||||
.map(node => node.nodeType);
|
||||
};
|
||||
|
||||
export const validateCategoryLevel = (
|
||||
selectedNodeType: string,
|
||||
parentCategoryPath: string[],
|
||||
tagOrder: string[]
|
||||
): boolean => {
|
||||
if (!isCategoryNodeType(selectedNodeType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parentCategoryPath.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const lastParentNodeType = parentCategoryPath[parentCategoryPath.length - 1];
|
||||
const selectedIndex = tagOrder.indexOf(selectedNodeType);
|
||||
const lastParentIndex = tagOrder.indexOf(lastParentNodeType);
|
||||
|
||||
if (selectedIndex === -1 || lastParentIndex === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return selectedIndex >= lastParentIndex;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user