feat: 机器人库、工业设计库、公差分析库

This commit is contained in:
JiangSheng
2026-02-09 08:52:38 +08:00
parent 417ecf2859
commit 0c7d2bf059
10 changed files with 966 additions and 582 deletions

View File

@@ -5,13 +5,8 @@
ref="tableFormRef"
:tableName="tableName"
v-model:data="formData"
:formAttrs="{
originalName: {
multiple: !isEdit,
limit: isEdit ? 1 : undefined,
},
}"
:itemNum="5"
:formAttrs="computedFormAttrs"
:itemNum="itemNum"
@change="onFormChangeFun"
>
<template #form-simulationPoolInfoList>
@@ -47,6 +42,8 @@ interface Props {
folder?: any;
tableName: string;
confirmLoading?: boolean;
moduleCode?: string;
itemNum?: number;
}
const props = withDefaults(defineProps<Props>(), {
@@ -55,16 +52,37 @@ const props = withDefaults(defineProps<Props>(), {
folder: null,
tableName: '',
confirmLoading: false,
moduleCode: '',
itemNum: 5,
});
const emits = defineEmits<{
'update:modelValue': [val: boolean];
confirm: [data: any];
}>();
const visible = computed({
get: () => props.modelValue,
set: (val) => emits('update:modelValue', val),
});
// formAttrs
const computedFormAttrs = computed(() => {
const attrs: any = {
originalName: {
multiple: !isEdit.value,
limit: isEdit.value ? 1 : undefined,
},
};
// templateId
if (props.moduleCode) {
attrs.templateId = {
moduleCode: props.moduleCode,
};
}
return attrs;
});
interface RuleForm {
originalName: any;
projectId: string | null;
@@ -74,6 +92,7 @@ interface RuleForm {
templateName?: string | null;
templateId?: string | null;
}
const formData = ref<RuleForm>({
originalName: null,
projectId: null,
@@ -83,11 +102,14 @@ const formData = ref<RuleForm>({
templateName: null,
templateId: null,
});
const isEdit = computed(() => !!(props.detail && props.detail.id));
const { isDirty, init: initSnapshot } = useDirtyForm(formData, {
mode: () => (isEdit.value ? 'edit' : 'add'),
pick: (data) => ({
originalName: data.originalName,
originalName:
data.originalName?.map?.(({ fileId, name }: any) => ({ fileId, name })) || data.originalName,
projectId: data.projectId,
analysisDirectionId: data.analysisDirectionId,
simulationPoolInfoList: data.simulationPoolInfoList,
@@ -119,6 +141,7 @@ const initData = () => {
formData.value.simulationPoolInfoList = props.detail?.poolInfos;
initSnapshot(formData.value);
};
watchEffect(() => {
nextTick(() => {
if (props.detail && props.detail.id) {
@@ -126,6 +149,7 @@ watchEffect(() => {
}
});
});
const resetFun = () => {
formData.value = {
originalName: null,
@@ -139,6 +163,7 @@ const resetFun = () => {
initSnapshot();
tableFormRef.value?.resetFun();
};
const onFormChangeFun = (data: any) => {
formData.value = tableFormRef.value.getFormDataFun();
if (data.key === 'templateId') {
@@ -146,10 +171,13 @@ const onFormChangeFun = (data: any) => {
formData.value.templateName = data?.val?.label;
}
};
const tableFormRef = ref<any>();
const onCancelFun = () => {
visible.value = false;
};
const onConfirmFun = async () => {
const valid = await tableFormRef.value?.validateFun();
if (valid) {
@@ -164,11 +192,11 @@ const onConfirmFun = async () => {
};
});
}
// return;
emits('confirm', formResult);
}
};
</script>
<style lang="scss" scoped>
.upload {
width: 100%;

View File

@@ -0,0 +1,118 @@
<template>
<Dialog
v-model="visible"
showFooter
showCancelButton
showConfirmButton
:diaTitle="mode === 'add' ? $t('知识库.新增') : $t('知识库.编辑')"
:confirmClosable="false"
:width="500"
>
<template #default>
<el-form ref="formRef" :rules="rules" :model="form" labelWidth="80">
<el-form-item :label="$t('知识库.名称')" prop="name">
<el-input v-model="form.name" />
</el-form-item>
</el-form>
</template>
<template #footer>
<div>
<el-button @click="onCancelFun">{{ $t('通用.取消') }}</el-button>
<el-button
type="primary"
:loading="confirmLoading"
:disabled="!isDirty"
@click="onConfirmFun"
>{{ $t('通用.确定') }}</el-button
>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue';
import Dialog from '@/components/common/dialog/index.vue';
import type { FormInstance, FormRules } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { useDirtyForm } from '@/utils/useDirtyForm';
const { t } = useI18n();
interface Props {
modelValue: boolean;
detail: any;
mode: 'add' | 'edit';
confirmLoading?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
detail: null,
mode: 'add',
confirmLoading: false,
});
const emits = defineEmits(['update:modelValue', 'confirm']);
const form = ref({
name: '',
});
const { isDirty, init: initSnapshot } = useDirtyForm(form, {
mode: () => props.mode,
});
const visible = computed({
get: () => props.modelValue,
set: (val) => emits('update:modelValue', val),
});
watch(
() => props.modelValue,
(val) => {
if (val) {
resetFun();
if (props.detail && props.mode === 'edit') {
form.value.name = props.detail.originalName || props.detail.nodeName;
initSnapshot(form.value);
} else {
form.value.name = '';
initSnapshot();
}
}
}
);
const resetFun = () => {
if (!formRef.value) {
return;
}
formRef.value.resetFields();
};
const formRef = ref<FormInstance>();
interface RuleForm {
name: string;
}
const rules = reactive<FormRules<RuleForm>>({
name: [{ required: true, message: t('知识库.请输入名称'), trigger: 'blur' }],
});
const onCancelFun = () => {
visible.value = false;
};
const onConfirmFun = async () => {
if (!formRef.value) {
return;
}
await formRef.value.validate((valid) => {
if (valid) {
emits('confirm', form.value);
}
});
};
</script>

View File

@@ -0,0 +1,44 @@
<template>
<el-drawer
v-model="visible"
:size="400"
:title="$t('知识库.基本信息')"
:closeOnClickModal="false"
@close="visible = false"
>
<div class="info-content">
<el-descriptions class="info-content-descriptions" :column="1" labelWidth="100px" border>
<el-descriptions-item :label="$t('知识库.目录名称')">
{{ data?.originalName }}
</el-descriptions-item>
<el-descriptions-item :label="$t('通用.创建人')">
{{ data?.creatorName }}
</el-descriptions-item>
<el-descriptions-item :label="$t('通用.创建时间')">
{{ data?.createTime }}
</el-descriptions-item>
<el-descriptions-item :label="$t('通用.更新人')">
{{ data?.updaterName }}
</el-descriptions-item>
<el-descriptions-item :label="$t('通用.更新时间')">
{{ data?.updateTime }}
</el-descriptions-item>
</el-descriptions>
</div>
</el-drawer>
</template>
<script lang="ts" setup>
const visible = defineModel<boolean>({ default: false });
defineProps<{ data?: any }>();
</script>
<style lang="scss" scoped>
.info-content {
width: 100%;
height: 100%;
.info-content-descriptions {
margin-top: var(--margin-small);
}
}
</style>

View File

@@ -0,0 +1,701 @@
<template>
<div class="gl-page-content-grey-full">
<div class="content">
<FileTree
ref="fileTreeRef"
:api="listDirApi"
:params="{ dirType: dirType }"
@choseNode="choseNodeFun"
@updateNav="updateNavFun"
>
<template #options>
<el-button :icon="Plus" class="options" type="primary" @click="appendFun({})">
{{ $t('知识库.新增') }}
</el-button>
</template>
<template #treeAction="{ data }">
<!-- 新增子目录 -->
<el-tooltip
v-if="parsePermission(data.permissionValue).write"
:content="$t('知识库.新增子目录')"
placement="top"
>
<el-button type="primary" link @click.stop="appendFun(data)">
<el-icon><FolderAdd /></el-icon>
</el-button>
</el-tooltip>
<!-- 编辑目录 -->
<el-tooltip
v-if="parsePermission(data.permissionValue).write"
:content="$t('通用.编辑')"
placement="top"
>
<el-button type="primary" link @click.stop="updateFun(data)">
<el-icon><EditPen /></el-icon>
</el-button>
</el-tooltip>
<!-- 删除目录 -->
<el-tooltip
v-if="parsePermission(data.permissionValue).delete"
:content="$t('通用.删除')"
placement="top"
>
<el-button type="danger" link @click.stop="confirmRemoveFun(data)">
<el-icon><Delete /></el-icon>
</el-button>
</el-tooltip>
</template>
<template #table>
<div class="right-table">
<div class="search">
<TableSearch
v-if="searchItems.length > 0"
ref="tableSearchRef"
:searchItems="searchItems"
@search="searchFun"
@reset="resetFun"
/>
</div>
<div class="table">
<BaseTable
ref="baseTableRef"
:tableName="tableName"
:api="apiName"
:params="searchParams"
showIndex
:actionList="actionList"
:exportApi="dataExportKnowledgeListApi"
fullHeight
:exportFileName="$t('知识库.知识库列表')"
:exportParams="{
...searchParams,
parentDirId: currentFolder?.id || '',
}"
@cell-dblclick="onCellDblclickFun"
>
<template #leftOptions>
<div>
<el-button
:icon="DArrowLeft"
:disabled="navList.length <= 1"
@click="backFun"
>{{ $t('通用.返回上一级') }}</el-button
>
<el-button
v-if="folderPermission.import"
type="primary"
:icon="Plus"
:disabled="!currentFolder"
@click="openModalFun"
>
{{ $t('知识库.上传') }}
</el-button>
<el-button :disabled="!currentFolder" @click="infoVisible = true">
{{ $t('知识库.基本信息') }}
</el-button>
</div>
</template>
<template #fileSize="{ row }">
{{ formatFileSize(row.fileSize) }}
</template>
<template #versionNo="{ row }">
{{ row.versionNo ? 'V' + row.versionNo : '--' }}
</template>
<template #poolInfos="{ row }">
<PoolTaskSelect :modelValue="row.poolInfos" :editable="false" />
</template>
<!-- 需要审批时显示审批状态 -->
<template v-if="needApproval" #approvalStatus="{ row, column }">
<template v-if="row.dataType === 2">
<el-button
v-if="row[column.field] === 'pending'"
type="primary"
link
@click="openProcessFun(row)"
>
{{ KNOWLEDGE_APPROVE_STATUS.O[row[column.field]] }}
</el-button>
<el-button v-else type="primary" link @click="openProcessFun(row)">
{{ $t('知识库.审批完成') }}
</el-button>
</template>
</template>
<template v-if="needApproval" #approveType="{ row, column }">
<el-button
v-if="row.dataType === 2"
type="primary"
link
@click="openProcessFun(row)"
>
{{ KNOWLEDGE_APPROVE_TYPE.O[row[column.field]] }}
</el-button>
</template>
</BaseTable>
</div>
</div>
</template>
</FileTree>
</div>
<!-- 审批流程弹窗仅需要审批时显示 -->
<ApprovalProcess v-if="needApproval" v-model="processVisible" :flowId="currentRow?.cidFlowId" />
<!-- 上传/编辑弹窗 -->
<DetailModal
v-model="visible"
:detail="currentRow"
:folder="currentFolder"
:tableName="tableName"
:moduleCode="moduleCode"
:itemNum="needApproval ? 6 : 5"
:confirmLoading="confirmLoading"
@confirm="onConfirmFun"
/>
<!-- 文件夹弹窗 -->
<FolderModal
v-model="folderModalVisible"
:detail="currentFolder"
:mode="currentFolderMode"
:confirmLoading="folderConfirmLoading"
@confirm="onFolderConfirmFun"
/>
<!-- 文件预览 -->
<FilePreview v-model="previewVisible" :fileId="currentRow?.id" />
<!-- 基本信息抽屉 -->
<InfoDrawer v-model="infoVisible" :data="currentFolder" />
<!-- 需要审批时的删除弹窗 -->
<ApproveDel
v-if="needApproval"
v-model="approveDelVisible"
:api="dataDelFileApi"
:params="approveDelParams"
:moduleCode="moduleCode"
@confirm="refreshTableFun"
/>
<ApproveDel
v-if="needApproval"
v-model="approveDelDirVisible"
:api="dataDelDirApi"
:params="approveDelDirParams"
:moduleCode="moduleCode"
@confirm="onDelDirConfirmFun"
/>
</div>
</template>
<script lang="ts" setup>
/**
* 资源库页面公共组件
* 适用于:动画库、机器人库、工业设计库、公差分析库等
* 通过 needApproval 区分是否需要审批流程
*/
import {
computed,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted,
ref,
watchEffect,
type Ref,
} from 'vue';
import { DArrowLeft, Plus } from '@element-plus/icons-vue';
import BaseTable from '@/components/common/table/baseTable.vue';
import FileTree from '@/components/common/fileTree/index.vue';
import DetailModal from './detailModal.vue';
import FolderModal from './folderModal.vue';
import InfoDrawer from './infoDrawer.vue';
import {
dataCreateDirApi,
dataListDirApi,
dataRenameDirApi,
dataDelDirApi,
dataQueryDirApi,
dataFileSearchApi,
dataDelFileApi,
dataUpdateFileApi,
batchAddFileInfoApi,
dataExportKnowledgeListApi,
isDirEmptyApi,
} from '@/api/data/data';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { RenderContentContext } from 'element-plus';
import { formatFileSize, downloadFileById } from '@/utils/file';
import TableSearch from '@/components/common/table/tableSearch.vue';
import { cloneDeep } from 'lodash-es';
import { useDict } from '@/utils/useDict';
import ApprovalProcess from '@/components/common/approvalProcess/index.vue';
import ApproveDel from '@/components/common/approveDel/index.vue';
import { useI18n } from 'vue-i18n';
import FilePreview from '@/components/common/filePreview/index.vue';
import emitter from '@/utils/eventBus';
import PoolTaskSelect from '@/components/pool/poolTaskSelect.vue';
import { parsePermission } from '@/utils/permission';
import { useRoute } from 'vue-router';
import { TABLE_NAME } from '@/utils/enum/tableName';
interface Props {
tableName?: string;
dirType: number;
needApproval?: boolean;
moduleCode?: string;
}
const props = withDefaults(defineProps<Props>(), {
tableName: TABLE_NAME.ANIMATION_LIBRARY,
needApproval: false,
moduleCode: '',
});
const route = useRoute();
const CALLBACK_FLAG = computed(() => route.path);
const listDirApi = (params: any) => {
return dataListDirApi({ ...params, dirType: props.dirType });
};
const folderPermission = computed(() =>
parsePermission(currentFolder.value?.permissionValue ?? 31)
);
const { t } = useI18n();
const { KNOWLEDGE_APPROVE_STATUS, KNOWLEDGE_APPROVE_TYPE } = useDict(
'KNOWLEDGE_APPROVE_STATUS',
'KNOWLEDGE_APPROVE_TYPE'
);
type Data = RenderContentContext['data'];
const fileTreeRef = ref<any>();
const baseTableRef = ref<any>();
const navList = ref<any>([]);
const currentFolder = ref();
const memoryFolder = ref();
const currentFolderMode: Ref<'add' | 'edit'> = ref('add');
const choseNodeFun = (data: Data) => {
isSearching.value = false;
currentFolder.value = data;
memoryFolder.value = currentFolder.value;
};
const updateNavFun = (data: any) => {
navList.value = data;
};
const backFun = () => {
fileTreeRef.value.backFun();
};
const searchItems = computed(() => [
{ title: t('通用.名称'), key: 'fileName', inputMode: 'input' },
{ title: t('知识库.上传人'), key: 'uploadUserId', inputMode: 'userSelectMultiple' },
]);
const folderModalVisible = ref(false);
const folderConfirmLoading = ref(false);
const appendFun = (data: Data) => {
currentFolder.value = data;
currentFolderMode.value = 'add';
folderModalVisible.value = true;
};
const updateFun = (data: Data) => {
currentFolder.value = data;
currentFolderMode.value = 'edit';
folderModalVisible.value = true;
};
// 删除目录确认
const confirmRemoveFun = async (data: Data) => {
if (props.needApproval) {
const res: any = await isDirEmptyApi({ dirId: data.id });
if (res.code !== 200) {
return;
}
if (res.data) {
ElMessageBox.confirm(t('知识库.确认删除空目录'), t('通用.提示'), {
confirmButtonText: t('通用.确定'),
cancelButtonText: t('通用.取消'),
type: 'warning',
})
.then(async () => {
const delRes: any = await dataDelDirApi({ delDirId: data.id });
if (delRes.code === 200) {
refreshTreeAfterModifyOrRemove(data);
ElMessage.success(delRes.message);
}
})
.catch(() => {});
} else {
ElMessageBox.confirm(t('知识库.目录内文件将一同被删除'), t('通用.提示'), {
confirmButtonText: t('通用.确定'),
cancelButtonText: t('通用.取消'),
type: 'warning',
})
.then(() => {
currentFolderToDelete.value = data;
approveDelDirParams.value = { delDirId: data.id };
approveDelDirVisible.value = true;
})
.catch(() => {});
}
} else {
// 不需要审批直接删除
ElMessageBox.confirm(t('知识库.确认删除'), t('通用.提示'), {
confirmButtonText: t('通用.确定'),
cancelButtonText: t('通用.取消'),
type: 'warning',
})
.then(() => removeFun(data))
.catch(() => {});
}
};
const removeFun = async (data: Data) => {
const res: any = await dataDelDirApi({ delDirId: data.id });
if (res.code === 200) {
refreshTreeAfterModifyOrRemove(data);
ElMessage.success(res.message);
}
};
const refreshTreeRoot = () => {
fileTreeRef.value?.reloadFun();
baseTableRef.value?.resetFun();
};
const refreshTreeOpenDir = (dirId: number) => {
fileTreeRef.value?.openDirFun(dirId);
baseTableRef.value?.resetFun();
};
const refreshTreeAfterAdd = (parentNode?: any | null) => {
if (parentNode && typeof parentNode.id === 'number') {
refreshTreeOpenDir(parentNode.id);
} else {
refreshTreeRoot();
}
};
const isTopLevel = (node: any) => {
if (!node || !node.objectKey) {
return false;
}
const key = String(node.objectKey).replace(/\/$/, '');
const parts = key.split('/');
return parts.length === 2;
};
const refreshTreeAfterModifyOrRemove = (node?: any | null) => {
if (isTopLevel(node)) {
refreshTreeRoot();
} else {
refreshTreeOpenDir(node.parentId);
}
};
const isSearching = ref(false);
const searchParams = ref<any>({
fileId: currentFolder.value?.id || '',
dirType: props.dirType,
});
// 操作按钮列表
const actionList = computed(() => {
const list = [
{
title: t('通用.预览'),
type: 'primary',
click: (row: any) => previewFileFun(row),
hide: (row: any) => row.dataType !== 2 || !parsePermission(row.permissionValue).read,
},
{
title: t('通用.下载'),
type: 'primary',
click: (row: any) => downloadFileById(row.id),
hide: (row: any) => row.dataType !== 2 || !parsePermission(row.permissionValue).export,
},
{
title: t('通用.编辑'),
type: 'primary',
click: (row: any) => editFileFun(row),
hide: (row: any) =>
row.dataType !== 2 ||
row.approvalStatus === 'pending' ||
!parsePermission(row.permissionValue).write,
},
];
// 删除按钮根据是否需要审批有不同逻辑
if (props.needApproval) {
list.push({
title: t('通用.删除'),
type: 'danger',
click: (row: any) => delFileWithApprovalFun(row),
hide: (row: any) =>
row.dataType !== 2 ||
row.approvalStatus === 'pending' ||
!parsePermission(row.permissionValue).delete,
});
} else {
list.push({
title: t('通用.删除'),
type: 'danger',
needConfirm: true,
confirmTip: t('通用.确认删除吗'),
click: (row: any) => delFileFun(row),
hide: (row: any) =>
row.dataType !== 2 ||
row.approvalStatus === 'pending' ||
!parsePermission(row.permissionValue).delete,
} as any);
}
return list;
});
const searchFun = (data: object) => {
isSearching.value = true;
if (!memoryFolder.value && currentFolder.value) {
memoryFolder.value = currentFolder.value;
currentFolder.value = null;
}
searchParams.value = cloneDeep(data);
searchParams.value.searchType = 1;
searchParams.value.parentDirId = memoryFolder.value?.id || '';
searchParams.value.dirType = props.dirType;
};
const resetFun = (data: object) => {
isSearching.value = false;
currentFolder.value = memoryFolder.value;
searchParams.value = cloneDeep(data);
searchParams.value.fileId = currentFolder.value?.id || '';
searchParams.value.dirType = props.dirType;
};
const refreshTableFun = async () => {
baseTableRef.value?.resetFun();
};
const onFolderConfirmFun = async (formData: any) => {
folderConfirmLoading.value = true;
try {
if (currentFolderMode.value === 'add') {
const req = {
dirType: props.dirType,
parDirId: currentFolder.value?.id,
dirName: formData.name,
type: 0,
};
const res: any = await dataCreateDirApi(req);
if (res.code === 200) {
refreshTreeAfterAdd(currentFolder.value);
folderModalVisible.value = false;
ElMessage.success(res.message);
}
} else if (currentFolderMode.value === 'edit') {
const req = {
dirId: currentFolder.value?.id,
oldName: currentFolder.value?.originalName,
newName: formData.name,
type: 0,
};
const res: any = await dataRenameDirApi(req);
if (res.code === 200) {
refreshTreeAfterModifyOrRemove(currentFolder.value);
folderModalVisible.value = false;
ElMessage.success(res.message);
}
}
} finally {
folderConfirmLoading.value = false;
}
};
const visible = ref(false);
const confirmLoading = ref(false);
const infoVisible = ref(false);
const currentRow = ref();
const openModalFun = () => {
visible.value = true;
currentRow.value = null;
};
const onConfirmFun = async (formData: any) => {
let res: any;
confirmLoading.value = true;
try {
if (currentRow.value && currentRow.value.id) {
// 编辑
const file = formData.file?.[0];
const req = {
...currentRow.value,
templateId: formData.templateId,
templateName: formData.templateName,
file: file?.raw,
fileName: file?.fileName,
size: file?.size,
projectName: formData.projectName,
path: currentFolder.value?.objectKey,
dirId: currentFolder.value?.id,
chunk: 0,
remarks: formData.remarks,
projectId: formData.projectId,
chunkTotal: 0,
encryptKey: null,
type: 0,
analysisDirectionId: formData.analysisDirectionId,
simulationPoolInfoListStr: JSON.stringify(formData.simulationPoolInfoList),
};
delete req.poolInfos;
res = await dataUpdateFileApi(req);
} else {
// 新增
const req = {
sourceFiles: formData.file,
uploadTaskId: new Date().getTime(),
dirId: currentFolder.value?.id,
projectId: formData.projectId,
analysisDirectionId: formData.analysisDirectionId,
templateId: formData.templateId,
templateName: formData.templateName,
remarks: formData.remarks,
type: 0,
simulationPoolInfoList: formData.simulationPoolInfoList,
};
res = await batchAddFileInfoApi(req);
if (res.code === 200) {
res.data.forEach((item: any, index: number) => {
emitter.emit('ADD_UPLOAD_FILE', {
file: formData.originalName[index].raw,
data: {
...item,
isApprove: props.needApproval ? 1 : 0,
taskType: 2,
callbackFlag: CALLBACK_FLAG.value,
},
});
});
}
}
if (res.code === 200) {
visible.value = false;
refreshTableFun();
ElMessage.success(res.message);
}
} finally {
confirmLoading.value = false;
}
};
const previewVisible = ref(false);
const previewFileFun = (row: any) => {
currentRow.value = row;
previewVisible.value = true;
};
const editFileFun = (row: any) => {
currentRow.value = row;
visible.value = true;
};
// 不需要审批的删除
const delFileFun = async (row: any) => {
currentRow.value = row;
try {
const res: any = await dataDelFileApi({ delFileId: row.id });
if (res.code === 200) {
refreshTableFun();
ElMessage.success(res.message);
}
} catch (error) {
console.error(error);
}
};
// 需要审批的删除
const approveDelVisible = ref(false);
const approveDelParams = ref<any>({});
const approveDelDirVisible = ref(false);
const approveDelDirParams = ref<any>({});
const currentFolderToDelete = ref<any>(null);
const delFileWithApprovalFun = (row: any) => {
currentRow.value = row;
approveDelParams.value = { delFileId: row.id };
approveDelVisible.value = true;
};
const onDelDirConfirmFun = () => {
refreshTreeAfterModifyOrRemove(currentFolderToDelete.value);
};
const onCellDblclickFun = (e: any) => {
if (e.row.dataType === 1) {
fileTreeRef.value?.openDirFun(e.row.id);
}
};
const apiName: any = ref(null);
watchEffect(() => {
if (currentFolder.value && currentFolder.value?.id && !isSearching.value) {
searchParams.value.fileId = currentFolder.value?.id || '';
apiName.value = dataQueryDirApi;
} else if (isSearching.value) {
apiName.value = dataFileSearchApi;
} else {
apiName.value = dataFileSearchApi;
}
});
// 审批流程弹窗
const processVisible = ref(false);
const openProcessFun = (row: any) => {
currentRow.value = row;
processVisible.value = true;
};
// 上传完成回调
const uploadFinishedFun = (data: any) => {
if (data.callbackFlag === CALLBACK_FLAG.value) {
refreshTableFun();
}
};
onMounted(() => {
emitter.on('UPLOAD_FINISHED', uploadFinishedFun);
});
onBeforeUnmount(() => {
emitter.off('UPLOAD_FINISHED', uploadFinishedFun);
});
onActivated(() => {
emitter.on('UPLOAD_FINISHED', uploadFinishedFun);
});
onDeactivated(() => {
emitter.off('UPLOAD_FINISHED', uploadFinishedFun);
});
</script>
<style lang="scss" scoped>
.gl-page-content-grey-full {
.content {
height: 100%;
.options {
margin-bottom: var(--margin-small);
}
.right-table {
display: flex;
flex-direction: column;
height: 100%;
.table {
flex: 1;
height: 0;
}
}
}
}
</style>

View File

@@ -235,12 +235,6 @@ export default [
name: 'CompetenceCenterKnowledge',
component: () => import('@/views/competenceCenter/knowledge/index.vue'),
},
{
title: '仿真动画库',
path: '/competenceCenter/animation',
name: 'CompetenceCenterAnimation',
component: () => import('@/tenants/lyric/views/competenceCenter/animation/index.vue'),
},
{
title: '仿真参数库',
path: '/competenceCenter/parameter',
@@ -259,6 +253,26 @@ export default [
name: 'CompetenceCenterAnimation',
component: () => import('@/tenants/lyric/views/competenceCenter/animation/index.vue'),
},
{
title: '机器人库',
path: '/competenceCenter/robot',
name: 'CompetenceCenterRobot',
component: () => import('@/tenants/lyric/views/competenceCenter/robot/index.vue'),
},
{
title: '工业设计库',
path: '/competenceCenter/industrialDesign',
name: 'CompetenceCenterIndustrialDesign',
component: () =>
import('@/tenants/lyric/views/competenceCenter/industrialDesign/index.vue'),
},
{
title: '公差分析库',
path: '/competenceCenter/toleranceAnalysis',
name: 'CompetenceCenterToleranceAnalysis',
component: () =>
import('@/tenants/lyric/views/competenceCenter/toleranceAnalysis/index.vue'),
},
// {
// title: '仿真流程库',
// path: '/simulation/process',

View File

@@ -1,561 +1,8 @@
<template>
<div class="gl-page-content-grey-full">
<div class="content">
<FileTree
ref="fileTreeRef"
:api="dataListDirAnimationApi"
:params="{ dirType: DIR_TYPE.ANIMATION }"
@choseNode="choseNodeFun"
@updateNav="updateNavFun"
>
<template #options>
<el-button :icon="Plus" class="options" type="primary" @click="appendFun({})">
{{ $t('知识库.新增') }}
</el-button>
</template>
<template #treeAction="{ data }">
<el-tooltip :content="$t('知识库.新增子目录')" placement="top">
<el-button type="primary" link @click.stop="appendFun(data)">
<el-icon>
<FolderAdd />
</el-icon>
</el-button>
</el-tooltip>
<el-tooltip :content="$t('通用.编辑')" placement="top">
<el-button type="primary" link @click.stop="updateFun(data)">
<el-icon>
<EditPen />
</el-icon>
</el-button>
</el-tooltip>
<el-tooltip :content="$t('通用.删除')" placement="top">
<el-button type="danger" link @click.stop="confirmRemoveFun(data)">
<el-icon>
<Delete />
</el-icon>
</el-button>
</el-tooltip>
</template>
<template #table>
<div class="right-table">
<div class="search">
<TableSearch
v-if="searchItems.length > 0"
ref="tableSearchRef"
:searchItems="searchItems"
@search="searchFun"
@reset="resetFun"
/>
</div>
<div class="table">
<BaseTable
ref="baseTableRef"
:tableName="TABLE_NAME.ANIMATION_LIBRARY"
:api="apiName"
:params="searchParams"
showIndex
:actionList="actionList"
:exportApi="dataExportKnowledgeListApi"
fullHeight
:exportFileName="$t('知识库.知识库列表')"
:exportParams="{
...searchParams,
parentDirId: currentFolder?.id || '',
}"
@cell-dblclick="onCellDblclickFun"
>
<template #leftOptions>
<div>
<el-button
:icon="DArrowLeft"
@click="backFun"
:disabled="navList.length <= 1"
>{{ $t('通用.返回上一级') }}</el-button
>
<el-button
type="primary"
:icon="Plus"
@click="openModalFun"
:disabled="!currentFolder"
>
{{ $t('知识库.上传') }}
</el-button>
<el-button @click="infoVisible = true" :disabled="!currentFolder">
{{ $t('知识库.基本信息') }}
</el-button>
</div>
</template>
<template #fileSize="{ row }">
{{ formatFileSize(row.fileSize) }}
</template>
<template #versionNo="{ row }">
{{ row.versionNo ? 'V' + row.versionNo : '--' }}
</template>
<template #poolInfos="{ row }">
<PoolTaskSelect :modelValue="row.poolInfos" :editable="false" />
</template>
<template #approvalStatus="{ row, column }">
<template v-if="row.dataType === 2">
<el-button
type="primary"
link
@click="openProcessFun(row)"
v-if="row[column.field] === 'pending'"
>
{{ KNOWLEDGE_APPROVE_STATUS.O[row[column.field]] }}
</el-button>
<el-button type="primary" link @click="openProcessFun(row)" v-else>
{{ $t('知识库.审批完成') }}
</el-button>
</template>
</template>
<template #approveType="{ row, column }">
<el-button
v-if="row.dataType === 2"
type="primary"
link
@click="openProcessFun(row)"
>
{{ KNOWLEDGE_APPROVE_TYPE.O[row[column.field]] }}</el-button
>
</template>
</BaseTable>
</div>
</div>
</template>
</FileTree>
</div>
<ApprovalProcess v-model="processVisible" :flowId="currentRow?.cidFlowId" />
<DetailModal
v-model="visible"
:detail="currentRow"
:folder="currentFolder"
:tableName="TABLE_NAME.ANIMATION_LIBRARY"
:confirmLoading="confirmLoading"
@confirm="onConfirmFun"
/>
<FolderModal
v-model="folderModalVisible"
:detail="currentFolder"
:mode="currentFolderMode"
:confirmLoading="folderConfirmLoading"
@confirm="onFolderConfirmFun"
/>
<FilePreview v-model="previewVisible" :fileId="currentRow?.id" />
<InfoDrawer v-model="infoVisible" :data="currentFolder" />
</div>
<ResourceLibraryPage :dirType="DIR_TYPE.ANIMATION" />
</template>
<script lang="ts" setup>
import {
computed,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted,
ref,
watchEffect,
type Ref,
} from 'vue';
import { DArrowLeft, Plus } from '@element-plus/icons-vue';
import BaseTable from '@/components/common/table/baseTable.vue';
import FileTree from '@/components/common/fileTree/index.vue';
import DetailModal from './components/detailModal.vue';
import FolderModal from '@/views/competenceCenter/knowledge/components/folderDetailModal.vue';
import InfoDrawer from '@/views/competenceCenter/knowledge/components/infoDrawer.vue';
import {
dataCreateDirApi,
dataListDirApi,
dataRenameDirApi,
dataDelDirApi,
dataQueryDirApi,
dataFileSearchApi,
dataDelFileApi,
dataUpdateFileApi,
batchAddFileInfoApi,
dataExportKnowledgeListApi,
} from '@/api/data/data';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { RenderContentContext } from 'element-plus';
import { formatFileSize } from '@/utils/file';
import TableSearch from '@/components/common/table/tableSearch.vue';
import { cloneDeep } from 'lodash-es';
import { DIR_TYPE } from '@/utils/enum/data.ts';
import { useDict } from '@/utils/useDict';
import ApprovalProcess from '@/components/common/approvalProcess/index.vue';
import { useI18n } from 'vue-i18n';
import FilePreview from '@/components/common/filePreview/index.vue';
import emitter from '@/utils/eventBus';
import { TABLE_NAME } from '@/utils/enum/tableName';
import { downloadFileById } from '@/utils/file';
import PoolTaskSelect from '@/components/pool/poolTaskSelect.vue';
const CALLBACK_FLAG = '/competenceCenter/animation';
const dataListDirAnimationApi = (params: any) => {
return dataListDirApi({ ...params, dirType: DIR_TYPE.ANIMATION });
};
const { t } = useI18n();
const { KNOWLEDGE_APPROVE_STATUS, KNOWLEDGE_APPROVE_TYPE } = useDict(
'KNOWLEDGE_APPROVE_STATUS',
'KNOWLEDGE_APPROVE_TYPE'
);
type Data = RenderContentContext['data'];
const fileTreeRef = ref<any>();
const baseTableRef = ref<any>();
const navList = ref<any>([]);
const currentFolder = ref();
const memoryFolder = ref();
const currentFolderMode: Ref<'add' | 'edit'> = ref('add');
const choseNodeFun = (data: Data) => {
isSearching.value = false;
currentFolder.value = data;
memoryFolder.value = currentFolder.value;
};
const updateNavFun = (data: any) => {
navList.value = data;
};
const backFun = () => {
fileTreeRef.value.backFun();
};
const searchItems = computed(() => [
{ title: t('通用.名称'), key: 'fileName', inputMode: 'input' },
{
title: t('知识库.上传人'),
key: 'uploadUserId',
inputMode: 'userSelectMultiple',
},
]);
const folderModalVisible = ref(false);
const folderConfirmLoading = ref(false);
const appendFun = (data: Data) => {
currentFolder.value = data;
currentFolderMode.value = 'add';
folderModalVisible.value = true;
};
const updateFun = (data: Data) => {
currentFolder.value = data;
currentFolderMode.value = 'edit';
folderModalVisible.value = true;
};
const confirmRemoveFun = (data: Data) => {
ElMessageBox.confirm(t('知识库.确认删除'), t('通用.提示'), {
confirmButtonText: t('通用.确定'),
cancelButtonText: t('通用.取消'),
type: 'warning',
})
.then(() => {
removeFun(data);
})
.catch(() => {});
};
const removeFun = async (data: Data) => {
const req = {
delDirId: data.id,
};
const res: any = await dataDelDirApi(req);
if (res.code === 200) {
refreshTreeAfterModifyOrRemove(data);
ElMessage.success(res.message);
}
};
const refreshTreeRoot = () => {
fileTreeRef.value?.reloadFun();
baseTableRef.value?.resetFun();
};
const refreshTreeOpenDir = (dirId: number) => {
fileTreeRef.value?.openDirFun(dirId);
baseTableRef.value?.resetFun();
};
const refreshTreeAfterAdd = (parentNode?: any | null) => {
if (parentNode && typeof parentNode.id === 'number') {
refreshTreeOpenDir(parentNode.id);
} else {
refreshTreeRoot();
}
};
const isTopLevel = (node: any) => {
if (!node || !node.objectKey) return false;
const key = String(node.objectKey).replace(/\/$/, '');
const parts = key.split('/');
return parts.length === 2;
};
const refreshTreeAfterModifyOrRemove = (node?: any | null) => {
if (isTopLevel(node)) {
refreshTreeRoot();
} else {
refreshTreeOpenDir(node.parentId);
}
};
const isSearching = ref(false);
const searchParams = ref<any>({
fileId: currentFolder.value?.id || '',
dirType: DIR_TYPE.ANIMATION,
});
const actionList = computed(() => {
return [
{
title: t('通用.预览'),
type: 'primary',
click: (row: any) => {
previewFileFun(row);
},
hide: (row: any) => {
return row.dataType !== 2;
},
},
{
title: t('通用.下载'),
type: 'primary',
click: (row: any) => {
downloadFileById(row.id);
},
hide: (row: any) => {
return row.dataType !== 2;
},
},
{
title: t('通用.编辑'),
type: 'primary',
click: (row: any) => {
editFileFun(row);
},
hide: (row: any) => {
return row.dataType !== 2 || row.approvalStatus === 'pending';
},
},
{
title: t('通用.删除'),
type: 'danger',
needConfirm: true,
confirmTip: t('通用.确认删除吗'),
click: (row: any) => {
delFileFun(row);
},
hide: (row: any) => {
return row.dataType !== 2 || row.approvalStatus === 'pending';
},
},
];
});
const searchFun = (data: object) => {
isSearching.value = true;
if (!memoryFolder.value && currentFolder.value) {
memoryFolder.value = currentFolder.value;
currentFolder.value = null;
}
searchParams.value = cloneDeep(data);
searchParams.value.searchType = 1;
searchParams.value.parentDirId = memoryFolder.value?.id || '';
searchParams.value.dirType = DIR_TYPE.ANIMATION;
};
const resetFun = (data: object) => {
isSearching.value = false;
currentFolder.value = memoryFolder.value;
searchParams.value = cloneDeep(data);
searchParams.value.fileId = currentFolder.value?.id || '';
searchParams.value.dirType = DIR_TYPE.ANIMATION;
};
const refreshTableFun = async () => {
baseTableRef.value?.resetFun();
};
const onFolderConfirmFun = async (formData: any) => {
folderConfirmLoading.value = true;
try {
if (currentFolderMode.value === 'add') {
const req = {
dirType: DIR_TYPE.ANIMATION,
parDirId: currentFolder.value?.id,
dirName: formData.name,
type: 0,
};
const res: any = await dataCreateDirApi(req);
if (res.code === 200) {
refreshTreeAfterAdd(currentFolder.value);
folderModalVisible.value = false;
ElMessage.success(res.message);
}
} else if (currentFolderMode.value === 'edit') {
const req = {
dirId: currentFolder.value?.id,
oldName: currentFolder.value?.originalName,
newName: formData.name,
type: 0,
};
const res: any = await dataRenameDirApi(req);
if (res.code === 200) {
refreshTreeAfterModifyOrRemove(currentFolder.value);
folderModalVisible.value = false;
ElMessage.success(res.message);
}
}
} finally {
folderConfirmLoading.value = false;
}
};
const visible = ref(false);
const confirmLoading = ref(false);
const infoVisible = ref(false);
const currentRow = ref();
const openModalFun = () => {
visible.value = true;
currentRow.value = null;
};
const onConfirmFun = async (formData: any) => {
let res: any;
confirmLoading.value = true;
try {
if (currentRow.value && currentRow.value.id) {
const file = formData.file?.[0];
const req = {
...currentRow.value,
templateId: formData.templateId,
templateName: formData.templateName,
file: file?.raw,
fileName: file?.fileName,
size: file?.size,
projectName: formData.projectName,
path: currentFolder.value?.objectKey,
dirId: currentFolder.value?.id,
chunk: 0,
remarks: formData.remarks,
projectId: formData.projectId,
chunkTotal: 0,
encryptKey: null,
type: 0,
analysisDirectionId: formData.analysisDirectionId,
simulationPoolInfoListStr: JSON.stringify(formData.simulationPoolInfoList),
};
delete req.poolInfos;
res = await dataUpdateFileApi(req);
} else {
const req = {
sourceFiles: formData.file,
uploadTaskId: new Date().getTime(),
dirId: currentFolder.value?.id,
projectId: formData.projectId,
analysisDirectionId: formData.analysisDirectionId,
templateId: formData.templateId,
templateName: formData.templateName,
remarks: formData.remarks,
type: 0,
simulationPoolInfoList: formData.simulationPoolInfoList,
};
res = await batchAddFileInfoApi(req);
if (res.code === 200) {
res.data.forEach((item: any, index: number) => {
emitter.emit('ADD_UPLOAD_FILE', {
file: formData.originalName[index].raw, // 文件对象
data: {
// 接口返回的文件目录信息,包括配置信息
...item,
isApprove: 1, // 0否 1是
taskType: 2, // 2知识库
callbackFlag: CALLBACK_FLAG,
},
});
});
}
}
if (res.code === 200) {
visible.value = false;
refreshTableFun();
ElMessage.success(res.message);
}
} finally {
confirmLoading.value = false;
}
};
const previewVisible = ref(false);
const previewFileFun = (row: any) => {
currentRow.value = row;
previewVisible.value = true;
};
const editFileFun = (row: any) => {
currentRow.value = row;
visible.value = true;
};
const delFileFun = async (row: any) => {
currentRow.value = row;
const req = { delFileId: row.id };
try {
const res: any = await dataDelFileApi(req);
if (res.code === 200) {
refreshTableFun();
ElMessage.success(res.message);
}
} catch (error) {
console.error(error);
}
};
const onCellDblclickFun = (e: any) => {
if (e.row.dataType === 1) {
fileTreeRef.value?.openDirFun(e.row.id);
}
};
const apiName: any = ref(null);
watchEffect(() => {
if (currentFolder.value && currentFolder.value?.id && !isSearching.value) {
searchParams.value.fileId = currentFolder.value?.id || '';
apiName.value = dataQueryDirApi;
} else if (isSearching.value) {
apiName.value = dataFileSearchApi;
} else {
apiName.value = dataFileSearchApi;
}
});
const processVisible = ref(false);
const openProcessFun = (row: any) => {
currentRow.value = row;
processVisible.value = true;
};
onMounted(() => {
emitter.on('UPLOAD_FINISHED', uploadFinishedFun);
});
onBeforeUnmount(() => {
emitter.off('UPLOAD_FINISHED', uploadFinishedFun);
});
onActivated(() => {
emitter.on('UPLOAD_FINISHED', uploadFinishedFun);
});
onDeactivated(() => {
emitter.off('UPLOAD_FINISHED', uploadFinishedFun);
});
const uploadFinishedFun = (data: any) => {
if (data.callbackFlag === CALLBACK_FLAG) {
refreshTableFun();
}
};
import ResourceLibraryPage from '@/components/common/resourceLibrary/resourceLibraryPage.vue';
import { DIR_TYPE } from '@/utils/enum/data';
</script>
<style lang="scss" scoped>
.gl-page-content-grey-full {
.content {
height: 100%;
.options {
margin-bottom: var(--margin-small);
}
.right-table {
display: flex;
flex-direction: column;
height: 100%;
.table {
flex: 1;
height: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,8 @@
<template>
<ResourceLibraryPage :dirType="DIR_TYPE.INDUSTRIAL_DESIGN" />
</template>
<script lang="ts" setup>
import ResourceLibraryPage from '@/components/common/resourceLibrary/resourceLibraryPage.vue';
import { DIR_TYPE } from '@/utils/enum/data';
</script>

View File

@@ -0,0 +1,8 @@
<template>
<ResourceLibraryPage :dirType="DIR_TYPE.ROBOT" />
</template>
<script lang="ts" setup>
import ResourceLibraryPage from '@/components/common/resourceLibrary/resourceLibraryPage.vue';
import { DIR_TYPE } from '@/utils/enum/data';
</script>

View File

@@ -0,0 +1,14 @@
<template>
<ResourceLibraryPage
:dirType="DIR_TYPE.TOLERANCE_ANALYSIS"
needApproval
moduleCode="TOLERANCE_APPROVAL"
:tableName="TABLE_NAME.SIMULATION_KNOWLEDGE"
/>
</template>
<script lang="ts" setup>
import ResourceLibraryPage from '@/components/common/resourceLibrary/resourceLibraryPage.vue';
import { DIR_TYPE } from '@/utils/enum/data';
import { TABLE_NAME } from '@/utils/enum/tableName';
</script>

View File

@@ -90,7 +90,7 @@
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import BaseTable from '@/components/common/table/baseTable.vue';
import FileTree from '@/components/common/fileTree/index.vue';
import {
@@ -99,6 +99,10 @@ import {
updateUserFilePermissionApi,
} from '@/api/data/data';
import { ElMessage } from 'element-plus';
import { DIR_TYPE } from '@/utils/enum/data';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const fileTreeRef = ref<any>();
const baseTableRef = ref<any>();
@@ -108,17 +112,15 @@ const filterUserName = ref<string>('');
const filterUserNameCopy = ref<string>('');
const currentTreeNodeInfo = ref<any>({});
const dirType = ref(1);
const dirType = ref(DIR_TYPE.KNOWLEDGE);
const dirTypeList = ref<any>([
{
label: '知识库',
value: 1,
},
{
label: '项目数据',
value: 2,
},
const dirTypeList = computed(() => [
{ label: t('菜单.仿真标准库'), value: DIR_TYPE.KNOWLEDGE },
{ label: t('项目列表.项目数据'), value: DIR_TYPE.PROJECT_NODE },
{ label: t('菜单.仿真动画库'), value: DIR_TYPE.ANIMATION },
{ label: t('菜单.机器人库'), value: DIR_TYPE.ROBOT },
{ label: t('菜单.工业设计库'), value: DIR_TYPE.INDUSTRIAL_DESIGN },
{ label: t('菜单.公差分析库'), value: DIR_TYPE.TOLERANCE_ANALYSIS },
]);
//