Merge branch 'main' of http://192.168.65.198:3000/Front_Team/SPDM
This commit is contained in:
27
src/App.vue
27
src/App.vue
@@ -1,7 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { RouterView, useRouter } from 'vue-router';
|
||||
import { VxeUI } from 'vxe-table';
|
||||
import { useDark } from '@vueuse/core';
|
||||
import i18n from '@/utils/i18n';
|
||||
|
||||
const isDark = useDark();
|
||||
const router = useRouter();
|
||||
const w: any = window;
|
||||
const $wujie: any = w.$wujie;
|
||||
@@ -9,11 +13,20 @@ const $wujie: any = w.$wujie;
|
||||
localStorage.setItem('USER_INFO_DATA', $wujie?.props?.USER_INFO_DATA || '{"tenant_id":"1979091834410176514","sub":"13866066913","iss":"https://honeycombis.com","active":true,"token_type":{"value":"Bearer"},"client_id":"pig","access_token":"Qt4aGUkre17RBhQ29MArqsjek51kb3i4DPaNiKJIYkiw9tKGvf8y2fzoBOXBd7KYQA6tm6mDquSfY8byft02x8IqaxQ-kquHmi9GHsWcM8EsE42MKKmjCZYMwvxW9hP6","refresh_token":"6D9kzwttTCkFLA-t9fcfXHa-G586RUjeA85wZk5y3rm2KG3--KwvJFHRuyRNRPrw32AOf1TKLT5Ei4Gssmu2BoLCQg-VvLqo0siaPdVNtAOfgSEuOG3-y8ARLz2Pc7Ku","aud":["pig"],"license":"https://honeycombis.com","nbf":"2025-11-06T07:59:44.541671288Z","user_id":"1980235559149838337","scope":["server"],"exp":"2025-12-26T08:04:44.541671288Z","iat":"2025-11-06T07:59:44.541671288Z","jti":"472dda93-5373-4f76-9d96-4fdef350e29c","username":"13866066913"}');
|
||||
|
||||
if ($wujie) {
|
||||
// 路径跳转
|
||||
$wujie.bus.$on('ROUTER_CHANGE', (data: any) => {
|
||||
router.push({
|
||||
path: data.path.split('/spdm')[1],
|
||||
});
|
||||
});
|
||||
// 语言变化
|
||||
$wujie.bus.$on('LANGUAGE_CHANGE', (lang: string) => {
|
||||
langToggleFun(lang === 'zh-cn' ? 'ZH' : 'EN');
|
||||
});
|
||||
// 黑色主题变化
|
||||
$wujie.bus.$on('DARK_THEME_CHANGE', (darkMode: boolean) => {
|
||||
darkToggleFun(darkMode);
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -21,6 +34,20 @@ onMounted(() => {
|
||||
$wujie.bus.$emit('SPMD_LOADED');
|
||||
}
|
||||
});
|
||||
|
||||
const langToggleFun = (lang: string) => {
|
||||
if (lang === 'ZH') {
|
||||
i18n.global.locale = 'ZH';
|
||||
} else {
|
||||
i18n.global.locale = 'EN';
|
||||
}
|
||||
localStorage.setItem('LANG_LOCALE', i18n.global.locale);
|
||||
};
|
||||
|
||||
const darkToggleFun = (darkMode: boolean) => {
|
||||
isDark.value = darkMode;
|
||||
VxeUI.setTheme(isDark.value ? 'dark' : 'light');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -25,23 +25,15 @@
|
||||
<div class="tree-item">
|
||||
<div class="name">
|
||||
<div class="icon">
|
||||
<template v-if="data.dataType === 1">
|
||||
<el-icon :size="18">
|
||||
<Folder v-if="!node.expanded" />
|
||||
<FolderOpened v-else />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template v-if="data.dataType === 2">
|
||||
<el-icon :size="18">
|
||||
<Document />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-icon :size="18">
|
||||
<Folder v-if="!node.expanded" />
|
||||
<FolderOpened v-else />
|
||||
</el-icon>
|
||||
</template>
|
||||
<el-icon v-if="data.nodeType === NODE_TYPE.PROJECT" :size="18">
|
||||
<MessageBox />
|
||||
</el-icon>
|
||||
<el-icon v-else-if="data.nodeType === NODE_TYPE.PHASE" :size="18">
|
||||
<Share />
|
||||
</el-icon>
|
||||
<el-icon v-else>
|
||||
<Folder />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="label">{{ data.nodeName }}</div>
|
||||
</div>
|
||||
@@ -76,7 +68,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { ArrowRight, Folder, FolderOpened, Document, DArrowLeft } from '@element-plus/icons-vue';
|
||||
import { MessageBox, ArrowRight, Share, Folder, DArrowLeft } from '@element-plus/icons-vue';
|
||||
import { NODE_TYPE } from '@/utils/enum/node';
|
||||
|
||||
interface Props {
|
||||
api: any;
|
||||
@@ -253,6 +246,7 @@ defineExpose({
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--el-text-color-secondary);
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
>
|
||||
<template #leftOptions v-if="!readonly">
|
||||
<div>
|
||||
<el-button :icon="Plus" @click="addRowFun(NODE_TYPE.CATEGORY)" :disabled="canAddChildFun(NODE_TYPE.CATEGORY)" >{{ $t('工况库.分类') }}</el-button>
|
||||
<el-button :icon="Plus" @click="addRowFun(NODE_TYPE.TASK)" :disabled="canAddChildFun(NODE_TYPE.TASK)">{{ $t('工况库.工况') }}</el-button>
|
||||
<el-button :icon="Plus" @click="addRowFun(NODE_TYPE.PERFORMANCE)" :disabled="canAddChildFun(NODE_TYPE.PERFORMANCE)" >{{ $t('工况库.指标') }}</el-button>
|
||||
<el-button :icon="Plus" @click="addRowFun(NODE_TYPE.CATEGORY)" :disabled="addNodeDisabled[NODE_TYPE.CATEGORY]" >{{ $t('工况库.分类') }}</el-button>
|
||||
<el-button :icon="Plus" @click="addRowFun(NODE_TYPE.TASK)" :disabled="addNodeDisabled[NODE_TYPE.TASK]">{{ $t('工况库.工况') }}</el-button>
|
||||
<el-button :icon="Plus" @click="addRowFun(NODE_TYPE.PERFORMANCE)" :disabled="addNodeDisabled[NODE_TYPE.PERFORMANCE]" >{{ $t('工况库.指标') }}</el-button>
|
||||
</div>
|
||||
<slot name="otherLeftOptions"></slot>
|
||||
</template>
|
||||
@@ -540,38 +540,24 @@ const checkboxChangeFun = (row:any) => {
|
||||
// canAddChildFun(checkRowData, NODE_TYPE.CATEGORY);
|
||||
// canAddChildFun(checkRowData, NODE_TYPE.TASK);
|
||||
// canAddChildFun(checkRowData, NODE_TYPE.PERFORMANCE);
|
||||
const checkRowData = loadcaseTableRef.value?.getCheckboxRecordsFun(true) || [];
|
||||
canAddChildFun(checkRowData, NODE_TYPE.CATEGORY);
|
||||
canAddChildFun(checkRowData, NODE_TYPE.TASK);
|
||||
canAddChildFun(checkRowData, NODE_TYPE.PERFORMANCE);
|
||||
};
|
||||
// const canAddChildFun = (checkRowData: any, nodeType: string) => {
|
||||
// if (!nodeType) {
|
||||
// return true;
|
||||
// }
|
||||
// let canAdd = true;
|
||||
// if (checkRowData.length === 0) {
|
||||
// canAdd = canAddChild({ nodeType: NODE_TYPE.ROOT }, nodeType);
|
||||
// } else if (checkRowData.length === 1) {
|
||||
// canAdd = canAddChild(checkRowData[0], nodeType);
|
||||
// } else {
|
||||
// canAdd = false;
|
||||
// }
|
||||
// addNodeDisabled.value[nodeType] = !canAdd;
|
||||
// };
|
||||
const canAddChildFun = (nodeType?: string) => {
|
||||
// return false;
|
||||
const canAddChildFun = (checkRowData: any, nodeType: string) => {
|
||||
if (!nodeType) {
|
||||
return true;
|
||||
}
|
||||
// const canAdd = true;
|
||||
// const checkRowData = getVxeRef()?.getCheckboxRecords(true);
|
||||
// const checkRowData = getVxeRef()?.getCheckboxRecords(true) || [];
|
||||
// if (checkRowData.length === 0) {
|
||||
// canAdd = canAddChild({ nodeType: NODE_TYPE.ROOT }, nodeType);
|
||||
// } else if (checkRowData.length === 1) {
|
||||
// canAdd = canAddChild(checkRowData[0], nodeType);
|
||||
// } else {
|
||||
// canAdd = false;
|
||||
// }
|
||||
// return !canAdd;
|
||||
|
||||
let canAdd = true;
|
||||
if (checkRowData.length === 0) {
|
||||
canAdd = canAddChild({ nodeType: NODE_TYPE.ROOT }, nodeType);
|
||||
} else if (checkRowData.length === 1) {
|
||||
canAdd = canAddChild(checkRowData[0], nodeType);
|
||||
} else {
|
||||
canAdd = false;
|
||||
}
|
||||
addNodeDisabled.value[nodeType] = !canAdd;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
|
||||
@@ -94,7 +94,7 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { CommonStore } from '@/stores/common';
|
||||
// import dayjs from 'dayjs';
|
||||
import { getTagMapList } from '../../projectDetail/components/project';
|
||||
import { getTagMapList } from '../../views/task/projectDetail/components/project';
|
||||
import { tableActionsLength } from '@/utils/common';
|
||||
import TableForm from '@/components/common/table/tableForm.vue';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="layout-content">
|
||||
<div class="layout-content" :class="{ preview: previewMode }">
|
||||
<div class="layout-body">
|
||||
<div class="layout-page">
|
||||
<RouterView v-if="!loading" />
|
||||
@@ -14,6 +14,10 @@ import { ref, onMounted } from 'vue';
|
||||
import { getDictionaryDataApi } from '@/api/system/systemData';
|
||||
import { CommonStore } from '@/stores/common';
|
||||
|
||||
const w: any = window;
|
||||
const $wujie: any = w.$wujie;
|
||||
const previewMode = $wujie?.props?.VIEW_MODE || '';
|
||||
|
||||
const store = CommonStore();
|
||||
const loading = ref(true);
|
||||
|
||||
@@ -61,6 +65,9 @@ const setDictDataFun = () => {
|
||||
width: 100%;
|
||||
height: calc(100vh - 104px);
|
||||
display: flex;
|
||||
&.preview {
|
||||
height: 100%;
|
||||
}
|
||||
.layout-body {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
|
||||
@@ -31,9 +31,9 @@ export const canAddChild = (parent: TreeNode, childType: string): boolean => {
|
||||
if (!parent || !parent.nodeType) {
|
||||
return false;
|
||||
}
|
||||
// if (parent.nodeType === NODE_TYPE.ROOT) {
|
||||
// return getAllowedChildrenTypes(parent.nodeType).includes(childType);
|
||||
// }
|
||||
if (parent.nodeType === NODE_TYPE.ROOT) {
|
||||
return getAllowedChildrenTypes(parent.nodeType).includes(childType);
|
||||
}
|
||||
const parentNodeType = isCategoryNodeType(parent.nodeType) ? NODE_TYPE.CATEGORY : parent.nodeType;
|
||||
const childNodeType = isCategoryNodeType(childType) ? NODE_TYPE.CATEGORY : childType;
|
||||
return getAllowedChildrenTypes(parentNodeType).includes(childNodeType);
|
||||
@@ -201,6 +201,12 @@ export const extractLeafNodesWithParentTypes = (list: any[] | undefined): any[]
|
||||
performance: 'performanceName',
|
||||
};
|
||||
|
||||
// 映射 nodeType -> 输出 code 字段名(仅 workspace 和 task)
|
||||
const typeToCodeField: Record<string, string> = {
|
||||
workspace: 'workspaceCode',
|
||||
task: 'taskCode',
|
||||
};
|
||||
|
||||
const walk = (nodes: any[] | undefined, parents: any[]) => {
|
||||
if (!Array.isArray(nodes) || nodes.length === 0) return;
|
||||
for (const node of nodes) {
|
||||
@@ -223,21 +229,48 @@ export const extractLeafNodesWithParentTypes = (list: any[] | undefined): any[]
|
||||
}
|
||||
});
|
||||
|
||||
// 如果叶子自身类型也是我们关注的类型(比如叶子就是 task),把自身的 nodeName 也合并进去
|
||||
const selfField = typeToField[node.nodeType];
|
||||
if (selfField) {
|
||||
namesMap[selfField] = namesMap[selfField] || [];
|
||||
// 收集 workspace/task 的 nodeCode(可能多个,需合并去重)
|
||||
const codesMap: Record<string, string[]> = {};
|
||||
parents.forEach(p => {
|
||||
if (!p || !p.nodeType) return;
|
||||
const codeField = typeToCodeField[p.nodeType];
|
||||
if (!codeField) return;
|
||||
codesMap[codeField] = codesMap[codeField] || [];
|
||||
const code = p.nodeCode || p.nodeCode === 0 ? String(p.nodeCode) : undefined;
|
||||
if (code && !codesMap[codeField].includes(code)) {
|
||||
codesMap[codeField].push(code);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果叶子自身类型也是我们关注的类型(比如叶子就是 task),把自身的 nodeName / nodeCode 也合并进去
|
||||
const selfNameField = typeToField[node.nodeType];
|
||||
if (selfNameField) {
|
||||
namesMap[selfNameField] = namesMap[selfNameField] || [];
|
||||
const name = node.nodeName || node.nodeName === 0 ? String(node.nodeName) : undefined;
|
||||
if (name && !namesMap[selfField].includes(name)) {
|
||||
namesMap[selfField].push(name);
|
||||
if (name && !namesMap[selfNameField].includes(name)) {
|
||||
namesMap[selfNameField].push(name);
|
||||
}
|
||||
}
|
||||
const selfCodeField = typeToCodeField[node.nodeType];
|
||||
if (selfCodeField) {
|
||||
codesMap[selfCodeField] = codesMap[selfCodeField] || [];
|
||||
const code = node.nodeCode || node.nodeCode === 0 ? String(node.nodeCode) : undefined;
|
||||
if (code && !codesMap[selfCodeField].includes(code)) {
|
||||
codesMap[selfCodeField].push(code);
|
||||
}
|
||||
}
|
||||
|
||||
// 将 namesMap 转为期望的字符串(若有多个同类祖先,则用 ' / ' 合并),没有则为 null
|
||||
const extraProps: Record<string, string | null> = {};
|
||||
Object.values(typeToField).forEach(field => {
|
||||
if (Array.isArray(namesMap[field]) && namesMap[field].length > 0) {
|
||||
extraProps[field] = namesMap[field].join(' / ');
|
||||
extraProps[field] = namesMap[field].join(' , ');
|
||||
} else {
|
||||
extraProps[field] = null;
|
||||
}
|
||||
});
|
||||
Object.values(typeToCodeField).forEach(field => {
|
||||
if (Array.isArray(codesMap[field]) && codesMap[field].length > 0) {
|
||||
extraProps[field] = codesMap[field].join(' , ');
|
||||
} else {
|
||||
extraProps[field] = null;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<el-form-item :label="$t('工况库.审核模版')" prop="approveTemplateId" v-if="form.bApprove === true">
|
||||
<ApproveList
|
||||
v-model="form.approveTemplateId"
|
||||
@change="onApproveChangeFun"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('工况库.升版')" prop="bNewVersion" >
|
||||
@@ -118,6 +119,9 @@ const onShowFun = () => {
|
||||
form.versionType = 0;
|
||||
};
|
||||
};
|
||||
const onApproveChangeFun = (data:any) => {
|
||||
form.approveTemplateName = data.label;
|
||||
};
|
||||
const resetFun = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
|
||||
@@ -91,8 +91,25 @@
|
||||
</div>
|
||||
</el-space>
|
||||
</div>
|
||||
<loadCaseTable v-show="currentTableType==='tree'" ref="treeTableRef" tableName="TASK_POOL" :modalTableNameList="['TASK_POOL_CATEGORY','TASK_POOL_TASK','TASK_POOL_PERFORMANCE']" :data="tableData" :loading="loading" :editMode="true" > </loadCaseTable>
|
||||
<loadCaseTable v-show="currentTableType==='list'" ref="listTableRef" readonly tableName="TASK_POOL_LIST" :data="extractTableData" :loading="loading" :editMode="false"> </loadCaseTable>
|
||||
<loadCaseTable
|
||||
v-show="currentTableType==='tree'"
|
||||
ref="treeTableRef"
|
||||
tableName="TASK_POOL"
|
||||
:modalTableNameList="['TASK_POOL_CATEGORY','TASK_POOL_TASK','TASK_POOL_PERFORMANCE']"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:editMode="true"
|
||||
> </loadCaseTable>
|
||||
<loadCaseTable
|
||||
v-show="currentTableType==='list'"
|
||||
ref="listTableRef"
|
||||
border="full"
|
||||
readonly
|
||||
tableName="TASK_POOL_LIST"
|
||||
:data="extractTableData"
|
||||
:loading="loading"
|
||||
:editMode="false"
|
||||
> </loadCaseTable>
|
||||
<loadcase-pro-table
|
||||
v-if="false"
|
||||
ref="treeTableRef2"
|
||||
@@ -109,6 +126,7 @@
|
||||
:showHeader="false"
|
||||
:isAdaptTableHeight="true"
|
||||
@refresh="refreshData"
|
||||
:readonly="true"
|
||||
>
|
||||
<template #toolbar_tools_extra>
|
||||
<span v-if="pageType === 'performance'">
|
||||
@@ -228,7 +246,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, onMounted, ref, type Ref } from 'vue';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import { cloneDeep, groupBy } from 'lodash-es';
|
||||
import { cloneDeep, groupBy, isEqual } from 'lodash-es';
|
||||
import { onBeforeRouteLeave, useRouter } from 'vue-router';
|
||||
import loadcaseProTable from '@/components/loadCaseTable/commonTable/loadcaseProTable.vue';
|
||||
import loadCaseTable from '@/components/common/treeCaseTable/loadCaseTable.vue';
|
||||
@@ -341,6 +359,7 @@ const refreshData = async (callback?: any) => {
|
||||
};
|
||||
|
||||
const treeTableRef = ref();
|
||||
const listTableRef = ref();
|
||||
const getVxeRef = () => {
|
||||
return treeTableRef?.value?.loadcaseTableRef?.TreeTableRef?.treeTableRef;
|
||||
};
|
||||
@@ -407,6 +426,7 @@ const createTaskPoolFun = async (formData: any) => {
|
||||
nodes: pickedNodes,
|
||||
bApprove: formData.bApprove,
|
||||
approveTemplateId: formData.approveTemplateId,
|
||||
approveTemplateName: formData.approveTemplateName,
|
||||
};
|
||||
const res: any = await createTaskPoolApi(req);
|
||||
if (res.code === 200) {
|
||||
@@ -419,6 +439,7 @@ const updateTaskPoolFun = async (formData: any) => {
|
||||
const req = {
|
||||
bApprove: formData.bApprove,
|
||||
approveTemplateId: formData.approveTemplateId,
|
||||
approveTemplateName: formData.approveTemplateName,
|
||||
bNewVersion: formData.bNewVersion,
|
||||
versionType: formData.versionType,
|
||||
poolBrief: {
|
||||
@@ -744,7 +765,12 @@ const queryTaskPoolFun = async () => {
|
||||
tableData.value = tree;
|
||||
isEmptyPool.value = false;
|
||||
extractTableData.value = extractLeafNodesWithParentTypes(res.data.nodes);
|
||||
const vxeInstance = listTableRef?.value?.loadcaseTableRef?.TreeTableRef?.treeTableRef;
|
||||
const mergeCells = calcMergeCellsFun(cloneDeep(extractTableData.value));
|
||||
console.log('extractTableData.value', extractTableData.value);
|
||||
console.log('mergeCells', mergeCells);
|
||||
nextTick(() => {
|
||||
vxeInstance.setMergeCells(mergeCells);
|
||||
// treeTableRef.value?.changeLevel('全部展开');
|
||||
});
|
||||
} else {
|
||||
@@ -752,6 +778,83 @@ const queryTaskPoolFun = async () => {
|
||||
isEmptyPool.value = true;
|
||||
}
|
||||
};
|
||||
const calcMergeCellsFun = (tableData: any) => {
|
||||
const mergeCells: any[] = [];
|
||||
if (!Array.isArray(tableData) || tableData.length === 0) return mergeCells;
|
||||
|
||||
const excludeFields = ['performanceName', 'operation', '_X_ROW_KEY', '_X_ROW_CHILD'];
|
||||
const isEmpty = (v: any) => v === null || v === undefined || v === '';
|
||||
|
||||
const vxeInstance = listTableRef?.value?.loadcaseTableRef?.TreeTableRef?.treeTableRef;
|
||||
let columnOrder: string[] = [];
|
||||
try {
|
||||
if (vxeInstance && typeof vxeInstance.getColumns === 'function') {
|
||||
const cols = vxeInstance.getColumns() || [];
|
||||
columnOrder = cols
|
||||
.filter((c: any) => {
|
||||
const t = c.type || c.colType || '';
|
||||
return !['checkbox', 'seq', 'expand', 'radio'].includes(t);
|
||||
})
|
||||
.map((c: any) => c.property || c.field || c.dataIndex || c.key || '')
|
||||
.filter(Boolean);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error('获取表格列顺序失败');
|
||||
}
|
||||
|
||||
const compCols =
|
||||
Array.isArray(tableColumns?.value) && tableColumns.value.length > 0
|
||||
? tableColumns.value.map((c: any) => c.field || c.dataIndex || c.key).filter(Boolean)
|
||||
: [];
|
||||
const rowKeys = Object.keys(tableData[0] || {});
|
||||
const fullOrder = columnOrder.length > 0 ? columnOrder : compCols.length > 0 ? compCols : rowKeys;
|
||||
|
||||
const fields = fullOrder.filter((f) => !!f && !excludeFields.includes(f));
|
||||
|
||||
const norm = (v: any) => {
|
||||
if (isEmpty(v)) return null;
|
||||
if (typeof v === 'object') return v;
|
||||
return String(v).trim();
|
||||
};
|
||||
const valuesEqual = (a: any, b: any) => {
|
||||
if (a === null || b === null) return false;
|
||||
if (typeof a === 'object' || typeof b === 'object') return isEqual(a, b);
|
||||
return a === b;
|
||||
};
|
||||
|
||||
fields.forEach((field) => {
|
||||
const colIdx = fullOrder.findIndex((f) => f === field);
|
||||
if (colIdx === -1) return;
|
||||
const n = tableData.length;
|
||||
|
||||
let start = 0;
|
||||
while (start < n && norm(tableData[start]?.[field]) === null) start++;
|
||||
if (start >= n) return;
|
||||
let startVal = norm(tableData[start][field]);
|
||||
|
||||
for (let i = start + 1; i <= n; i++) {
|
||||
const currVal = i < n ? norm(tableData[i]?.[field]) : null;
|
||||
const equal = i < n && valuesEqual(startVal, currVal);
|
||||
|
||||
if (!equal) {
|
||||
const rowspan = i - start;
|
||||
if (rowspan > 1 && startVal !== null) {
|
||||
mergeCells.push({
|
||||
row: start,
|
||||
col: colIdx + 1,
|
||||
rowspan,
|
||||
colspan: 1,
|
||||
});
|
||||
}
|
||||
start = i;
|
||||
while (start < n && norm(tableData[start]?.[field]) === null) start++;
|
||||
startVal = start < n ? norm(tableData[start][field]) : null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return mergeCells;
|
||||
};
|
||||
|
||||
const queryTaskPoolPerformanceFun = async () => {
|
||||
performanceLoading.value = true;
|
||||
@@ -784,7 +887,7 @@ const queryVersionsFun = async () => {
|
||||
};
|
||||
const res: any = await getTaskPoolVersionsApi(req);
|
||||
if (res.code === 200 && res.data && Array.isArray(res.data) && res.data.length > 0) {
|
||||
versionList.value = res.data;
|
||||
versionList.value = res.data.reverse();
|
||||
if (versionList.value.length > 0) {
|
||||
currentPoolBriefVersion.value = versionList.value[0];
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="showProjectInfoDialog = true">新增项目</el-dropdown-item>
|
||||
<el-dropdown-item @click="openNodeFun">新增阶段</el-dropdown-item>
|
||||
<el-dropdown-item>新增分类</el-dropdown-item>
|
||||
<el-dropdown-item @click="modalVisible = true">新增分类</el-dropdown-item>
|
||||
<el-dropdown-item>新增任务</el-dropdown-item>
|
||||
<!-- <el-dropdown-item>新增算例</el-dropdown-item> -->
|
||||
</el-dropdown-menu>
|
||||
@@ -77,10 +77,21 @@
|
||||
</template>
|
||||
</FileTree>
|
||||
</div>
|
||||
<Configuration v-model="configurationShow" :dimensionId="currentDimension" />
|
||||
<FileSearch v-model="searchShow" />
|
||||
<FileInfo v-model="infoShow" />
|
||||
<AddDir v-model="addDirShow" :data="currentData" @submit="reloadFun" />
|
||||
<Configuration
|
||||
v-model="configurationShow"
|
||||
:dimensionId="currentDimension"
|
||||
/>
|
||||
<FileSearch
|
||||
v-model="searchShow"
|
||||
/>
|
||||
<FileInfo
|
||||
v-model="infoShow"
|
||||
/>
|
||||
<AddDir
|
||||
v-model="addDirShow"
|
||||
:data="currentData"
|
||||
@submit="reloadFun"
|
||||
/>
|
||||
<PhaseInfoDia
|
||||
v-model:showNodeInfoDialog="showNodeInfoDialog"
|
||||
:nodeLevel1Uuid="currentData.uuid"
|
||||
@@ -88,10 +99,16 @@
|
||||
/>
|
||||
<ProjectInfoDialog
|
||||
v-model="showProjectInfoDialog"
|
||||
dialogType="create"
|
||||
:projectId="currentData.uuid"
|
||||
:nodeLevel1List="[]"
|
||||
@completeFun="reloadFun"
|
||||
/>
|
||||
<nodeDetailDialog
|
||||
v-model="modalVisible"
|
||||
tableName="PROJECT_TASK_MODAL"
|
||||
diaTitle="新增"
|
||||
:detail="{}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -105,7 +122,8 @@ import Configuration from './components/configuration.vue';
|
||||
import FileSearch from './components/search.vue';
|
||||
import FileInfo from './components/info.vue';
|
||||
import AddDir from './components/addDir.vue';
|
||||
import PhaseInfoDia from '@/views/task/projectList/components/phaseInfoDialog.vue';
|
||||
import PhaseInfoDia from '@/components/project/phaseInfoDialog.vue';
|
||||
import nodeDetailDialog from '@/components/common/treeCaseTable/nodeDetailDialog.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import ProjectInfoDialog from '@/components/project/projectInfoDialog.vue';
|
||||
import { CirclePlus, Upload, Search, Edit, Refresh, Delete, Warning } from '@element-plus/icons-vue';
|
||||
@@ -119,6 +137,7 @@ const infoShow = ref(false);
|
||||
const showProjectInfoDialog = ref(false);
|
||||
const showNodeInfoDialog = ref(false);
|
||||
const addDirShow = ref(false);
|
||||
const modalVisible = ref(false);
|
||||
const currentData = ref<any>('');
|
||||
const FileTreeRef = ref();
|
||||
let chosenData: any[] = [];
|
||||
@@ -137,10 +156,16 @@ const getAllTemplateFun = () => {
|
||||
};
|
||||
|
||||
const editFun = () => {
|
||||
if (chosenData.length !== 1) {
|
||||
ElMessage.warning('请选择一条数据操作');
|
||||
if (!currentData.value) {
|
||||
ElMessage.warning('请选择一个目录');
|
||||
return;
|
||||
}
|
||||
if (currentData.value.nodeType === NODE_TYPE.PROJECT) {
|
||||
showProjectInfoDialog.value = true;
|
||||
}
|
||||
if (currentData.value.nodeType === NODE_TYPE.PHASE) {
|
||||
showNodeInfoDialog.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const delFun = () => {
|
||||
|
||||
@@ -44,6 +44,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #operate="{row}">
|
||||
<el-button link type="dnager" @click="deleteFileFn(row)">删除</el-button>
|
||||
</template>
|
||||
|
||||
</BaseTable>
|
||||
</div>
|
||||
</template>
|
||||
@@ -98,6 +102,17 @@ const deleteFileBatchFn = async () => {
|
||||
|
||||
};
|
||||
|
||||
const deleteFileFn = async (row:any) => {
|
||||
const param = [row.id];
|
||||
|
||||
const res:any = await batchDeleteBigFileApi(param);
|
||||
if (res && res.code === 200) {
|
||||
ElMessage.success('删除成功');
|
||||
searchFn();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
<template>
|
||||
<div class="storage-page">
|
||||
<div class="operate-box">
|
||||
|
||||
<div class="targetYm-date">
|
||||
<div class="title" >时间范围:</div>
|
||||
<el-radio-group v-model="formData.intervalMonths" @change="intervalMonthsChangeFn">
|
||||
<el-radio-button v-for="item in monthList" :key="item.value" :value="item.value" :label="item.name" />
|
||||
</el-radio-group>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="targetYm-date" v-if="radio === 'increment'">
|
||||
<div class="title" >增量月份:</div>
|
||||
<el-date-picker
|
||||
class="date-style"
|
||||
v-model="formData.targetYm"
|
||||
type="month"
|
||||
placeholder="请请选择月份"
|
||||
format="YYYY-MM"
|
||||
value-format="YYYY-MM"
|
||||
@change="intervalMonthsChangeFn"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-radio-group v-model="radio" @change="radioChangeFn">
|
||||
<el-radio value="default" size="large">默认</el-radio>
|
||||
<el-radio value="increment" size="large">增量</el-radio>
|
||||
@@ -9,78 +31,132 @@
|
||||
|
||||
<!-- 项目存储空间统计 -->
|
||||
<div class="chart-item" >
|
||||
<div class="chart-filter">
|
||||
<span>项目:</span>
|
||||
<div class="project-name-content">
|
||||
<ProjectSelect
|
||||
class="select-width margin-right-12"
|
||||
v-model="projectStorageSpaceStatisticsFormData.nodeId"
|
||||
@change="initProjectStorageSpaceStatisticsFn"
|
||||
/>
|
||||
</div>
|
||||
<div class="chart-content" >
|
||||
<EchartCard title="项目存储空间统计" ref="projectStorageSpaceStatisticsRef" :charts-id="'chart1'" :bar-type="'barChart'">
|
||||
<template #formSlot>
|
||||
<el-form :model="projectStorageSpaceStatisticsFormData" :inline="true">
|
||||
<el-form-item label="项目:">
|
||||
<ProjectSelect
|
||||
class="select-width margin-right-12"
|
||||
v-model="projectStorageSpaceStatisticsFormData.nodeId"
|
||||
@change="initProjectStorageSpaceStatisticsFn"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</template>
|
||||
|
||||
</EchartCard>
|
||||
|
||||
</div>
|
||||
<div class="chart-content" id="chart1" ref="projectStorageSpaceStatisticsRef"></div>
|
||||
</div>
|
||||
|
||||
<!-- 学科存储空间统计 -->
|
||||
<div class="chart-item" >
|
||||
<div class="chart-filter">
|
||||
<span>学科:</span>
|
||||
<div class="project-name-content">
|
||||
<el-select v-model="statisticsOfSubjectStorageSpaceFormData.nodeId" @change="initStatisticsOfSubjectStorageSpaceFn">
|
||||
<el-option v-for="item in disciplineOptions" :label="item.label" :value="item.value" :key="item.value"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="chart-content" >
|
||||
<EchartCard title="学科存储空间统计" ref="statisticsOfSubjectStorageSpaceRef" :charts-id="'chart2'" :bar-type="'barChart'">
|
||||
<template #formSlot>
|
||||
<el-form :model="statisticsOfSubjectStorageSpaceFormData" :inline="true">
|
||||
<el-form-item label="学科:">
|
||||
<el-select class="select-width" v-model="statisticsOfSubjectStorageSpaceFormData.nodeId" @change="initStatisticsOfSubjectStorageSpaceFn">
|
||||
<el-option v-for="item in disciplineOptions" :label="item.label" :value="item.value" :key="item.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</EchartCard>
|
||||
|
||||
</div>
|
||||
<div class="chart-content" id="chart2" ref="statisticsOfSubjectStorageSpaceRef"></div>
|
||||
</div>
|
||||
|
||||
<!-- 用户存储空间统计 -->
|
||||
<div class="chart-item" >
|
||||
<div class="chart-filter">
|
||||
<span>用户组:</span>
|
||||
<div class="project-name-content mr10" >
|
||||
<el-select
|
||||
clearable
|
||||
class="select-width margin-right-12"
|
||||
v-model="userStorageSpaceStatisticsFormData.userGroupId"
|
||||
@change="initUserStorageSpaceStatisticsFn"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userGroupOptions"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:key="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<span>用户:</span>
|
||||
<div class="project-name-content">
|
||||
<UserSelect
|
||||
class="select-width"
|
||||
v-model="userStorageSpaceStatisticsFormData.userIds"
|
||||
@change="initUserStorageSpaceStatisticsFn"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="chart-content" >
|
||||
<EchartCard title="用户存储空间统计" ref="userStorageSpaceStatisticsRef" :charts-id="'chart3'" :bar-type="'barChart'">
|
||||
<template #formSlot>
|
||||
<el-form :model="userStorageSpaceStatisticsFormData" :inline="true">
|
||||
<el-form-item label="用户组:">
|
||||
<el-select
|
||||
clearable
|
||||
class="select-width margin-right-12"
|
||||
v-model="userStorageSpaceStatisticsFormData.userGroupId"
|
||||
@change="initUserStorageSpaceStatisticsFn"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userGroupOptions"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:key="item.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户:">
|
||||
<UserSelect
|
||||
class="select-width"
|
||||
v-model="userStorageSpaceStatisticsFormData.userIds"
|
||||
@change="initUserStorageSpaceStatisticsFn"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
</EchartCard>
|
||||
|
||||
</div>
|
||||
<div class="chart-content" id="chart3" ref="userStorageSpaceStatisticsRef"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps, defineEmits, reactive, onMounted } from 'vue';
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue';
|
||||
import ProjectSelect from '@/components/common/projectSelect/index.vue';
|
||||
import UserSelect from '@/components/common/userSelect/index.vue';
|
||||
import { queryNodeListApi } from '@/api/project/node';
|
||||
import { userQueryGroupApi } from '@/api/system/user';
|
||||
import { getDirectorySizeByUserIdApi, getNodeSizeByNodeTypeApi } from '@/api/data/data';
|
||||
import EchartCard from '@/components/common/echartCard/index.vue';
|
||||
|
||||
const radio = ref('default');
|
||||
|
||||
const radioChangeFn = () => {
|
||||
const formData = reactive({
|
||||
targetYm: '',
|
||||
intervalMonths: 6,
|
||||
});
|
||||
|
||||
const monthList = ref([
|
||||
{
|
||||
value: 6,
|
||||
name: '近6个月',
|
||||
},
|
||||
{
|
||||
value: 12,
|
||||
name: '近1年',
|
||||
},
|
||||
{
|
||||
value: 24,
|
||||
name: '近2年',
|
||||
},
|
||||
{
|
||||
value: 36,
|
||||
name: '近3年',
|
||||
},
|
||||
]);
|
||||
|
||||
const radioChangeFn = async () => {
|
||||
formData.targetYm = '';
|
||||
|
||||
await initProjectStorageSpaceStatisticsFn();
|
||||
await initStatisticsOfSubjectStorageSpaceFn();
|
||||
await initUserStorageSpaceStatisticsFn();
|
||||
};
|
||||
|
||||
const intervalMonthsChangeFn = async () => {
|
||||
await initProjectStorageSpaceStatisticsFn();
|
||||
await initStatisticsOfSubjectStorageSpaceFn();
|
||||
await initUserStorageSpaceStatisticsFn();
|
||||
};
|
||||
|
||||
// 项目存储空间统计
|
||||
@@ -91,6 +167,73 @@ const projectStorageSpaceStatisticsFormData = reactive({
|
||||
|
||||
const initProjectStorageSpaceStatisticsFn = async () => {
|
||||
|
||||
const xData:any = [];
|
||||
const seriesData:any = [];
|
||||
|
||||
const res:any = await getNodeSizeByNodeTypeApi({
|
||||
queryNodeType: 'project',
|
||||
nodeId: projectStorageSpaceStatisticsFormData.nodeId,
|
||||
intervalMonths: formData.intervalMonths,
|
||||
targetYm: formData.targetYm,
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
|
||||
}
|
||||
|
||||
projectStorageSpaceStatisticsRef.value.commonChartRef.disposeEchartsByKey('chart1');
|
||||
|
||||
projectStorageSpaceStatisticsRef.value.commonChartRef.option = {
|
||||
title: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
top: '0%',
|
||||
left: 'center',
|
||||
|
||||
},
|
||||
grid: {
|
||||
top: '10%',
|
||||
bottom: '10%',
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
dataZoom:
|
||||
xData.length > 4
|
||||
? [
|
||||
{
|
||||
type: 'slider',
|
||||
show: true,
|
||||
xAxisIndex: [0],
|
||||
start: 0,
|
||||
end: 100,
|
||||
textStyle: {
|
||||
color: 'transparent',
|
||||
},
|
||||
maxValueSpan: 4,
|
||||
minValueSpan: 4,
|
||||
moveHandleSize: 10,
|
||||
height: 0,
|
||||
filterMode: 'empty',
|
||||
bottom: 15,
|
||||
},
|
||||
]
|
||||
: null,
|
||||
series: seriesData,
|
||||
};
|
||||
projectStorageSpaceStatisticsRef.value.commonChartRef.initChart();
|
||||
|
||||
};
|
||||
|
||||
// 学科存储空间统计
|
||||
@@ -100,7 +243,71 @@ const statisticsOfSubjectStorageSpaceFormData = reactive({
|
||||
});
|
||||
|
||||
const initStatisticsOfSubjectStorageSpaceFn = async () => {
|
||||
const xData:any = [];
|
||||
const seriesData:any = [];
|
||||
const res:any = await getNodeSizeByNodeTypeApi({
|
||||
queryNodeType: 'discipline',
|
||||
nodeId: projectStorageSpaceStatisticsFormData.nodeId,
|
||||
intervalMonths: formData.intervalMonths,
|
||||
targetYm: formData.targetYm,
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
|
||||
}
|
||||
|
||||
statisticsOfSubjectStorageSpaceRef.value.commonChartRef.disposeEchartsByKey('chart2');
|
||||
|
||||
statisticsOfSubjectStorageSpaceRef.value.commonChartRef.option = {
|
||||
title: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
top: '0%',
|
||||
left: 'center',
|
||||
|
||||
},
|
||||
grid: {
|
||||
top: '10%',
|
||||
bottom: '10%',
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
dataZoom:
|
||||
xData.length > 4
|
||||
? [
|
||||
{
|
||||
type: 'slider',
|
||||
show: true,
|
||||
xAxisIndex: [0],
|
||||
start: 0,
|
||||
end: 100,
|
||||
textStyle: {
|
||||
color: 'transparent',
|
||||
},
|
||||
maxValueSpan: 4,
|
||||
minValueSpan: 4,
|
||||
moveHandleSize: 10,
|
||||
height: 0,
|
||||
filterMode: 'empty',
|
||||
bottom: 15,
|
||||
},
|
||||
]
|
||||
: null,
|
||||
series: seriesData,
|
||||
};
|
||||
statisticsOfSubjectStorageSpaceRef.value.commonChartRef.initChart();
|
||||
};
|
||||
|
||||
// 用户存储空间统计
|
||||
@@ -111,7 +318,70 @@ const userStorageSpaceStatisticsFormData = reactive({
|
||||
});
|
||||
|
||||
const initUserStorageSpaceStatisticsFn = async () => {
|
||||
const xData:any = [];
|
||||
const seriesData:any = [];
|
||||
const res:any = await getDirectorySizeByUserIdApi({
|
||||
userIds: userStorageSpaceStatisticsFormData.userIds,
|
||||
intervalMonths: formData.intervalMonths,
|
||||
targetYm: formData.targetYm,
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
|
||||
}
|
||||
|
||||
userStorageSpaceStatisticsRef.value.commonChartRef.disposeEchartsByKey('chart3');
|
||||
|
||||
userStorageSpaceStatisticsRef.value.commonChartRef.option = {
|
||||
title: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
top: '0%',
|
||||
left: 'center',
|
||||
|
||||
},
|
||||
grid: {
|
||||
top: '10%',
|
||||
bottom: '10%',
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xData,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
dataZoom:
|
||||
xData.length > 4
|
||||
? [
|
||||
{
|
||||
type: 'slider',
|
||||
show: true,
|
||||
xAxisIndex: [0],
|
||||
start: 0,
|
||||
end: 100,
|
||||
textStyle: {
|
||||
color: 'transparent',
|
||||
},
|
||||
maxValueSpan: 4,
|
||||
minValueSpan: 4,
|
||||
moveHandleSize: 10,
|
||||
height: 0,
|
||||
filterMode: 'empty',
|
||||
bottom: 15,
|
||||
},
|
||||
]
|
||||
: null,
|
||||
series: seriesData,
|
||||
};
|
||||
userStorageSpaceStatisticsRef.value.commonChartRef.initChart();
|
||||
};
|
||||
|
||||
const disciplineOptions = ref<any>([]);
|
||||
@@ -151,6 +421,12 @@ const getGroupList = async () => {
|
||||
onMounted(async () => {
|
||||
await getDisciplineOptionsFun();
|
||||
await getGroupList();
|
||||
|
||||
nextTick(() => {
|
||||
initProjectStorageSpaceStatisticsFn();
|
||||
initStatisticsOfSubjectStorageSpaceFn();
|
||||
initUserStorageSpaceStatisticsFn();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
@@ -162,6 +438,10 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.select-width{
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.operate-box{
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
@@ -170,6 +450,22 @@ onMounted(async () => {
|
||||
justify-content: flex-end;
|
||||
padding-right: 10px;
|
||||
|
||||
.targetYm-date{
|
||||
display: flex;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 12px;
|
||||
|
||||
.title{
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
:deep(.date-style){
|
||||
width: 150px !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.chart-item {
|
||||
@@ -197,6 +493,11 @@ onMounted(async () => {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.chart-content{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -95,7 +95,7 @@ import projectFile from './components/projectFile.vue';
|
||||
import teamMember from './components/teamMember.vue';
|
||||
import loadcase from './components/loadcase.vue';
|
||||
import projectInfoDialog from '@/components/project/projectInfoDialog.vue';
|
||||
import nodeInfoDialog from '../projectList/components/phaseInfoDialog.vue';
|
||||
import nodeInfoDialog from '@/components/project/phaseInfoDialog.vue';
|
||||
import taskDialog from '../projectList/components/taskDialog.vue';
|
||||
import baseInfo from './components/baseInfo.vue';
|
||||
import { editNodeApi } from '@/api/project/node';
|
||||
|
||||
Reference in New Issue
Block a user