Files
SPDM/src/views/competenceCenter/knowledge/index.vue
2025-12-01 11:27:06 +08:00

476 lines
14 KiB
Vue

<template>
<div class="gl-page-content-grey">
<div class="content">
<FileTree
ref="fileTreeRef"
:api="dataListDirApi"
:params="{ dirType: DIR_TYPE.KNOWLEDGE }"
@choseNode="choseNodeFun"
@updateNav="updateNavFun"
>
<template #options>
<el-button :icon="Plus" class="options" type="primary" @click="appendFun({})">
{{ $t('知识库.新增') }}
</el-button>
</template>
<template #treeAction="{ data, }">
<div>
<el-button type="primary" link @click.stop="appendFun(data)">
<el-icon>
<FolderAdd />
</el-icon>
</el-button>
<el-button type="primary" link @click.stop="updateFun(data)">
<el-icon>
<EditPen />
</el-icon>
</el-button>
<el-popconfirm
:title="$t('知识库.确认删除')"
:cancelButtonText="$t('通用.取消')"
:confirmButtonText="$t('通用.确定')"
@confirm="removeFun(data)"
>
<template #reference>
<el-button type="danger" link>
<el-icon>
<Delete />
</el-icon>
</el-button>
</template>
</el-popconfirm>
</div>
</template>
<template #table>
<TableSearch
v-if="searchItems.length > 0"
ref="tableSearchRef"
:searchItems="searchItems"
@search="searchFun"
@reset="resetFun"
/>
<BaseTable
ref="baseTableRef"
tableName="SIMULATION_KNOWLEDGE"
:api="apiName"
:params="searchParams"
showIndex
:actionsWidth="200"
:actionList="actionList"
@cell-dblclick="onCellDblclickFun"
:exportApi="dataExportKnowledgeListApi"
:exportFileName="$t('知识库.知识库列表')"
:exportDict="{
approvalStatus: 'KNOWLEDGE_APPROVE_STATUS'
}"
:exportParams="{
...searchParams,
parentDirId: currentFolder?.id || ''
}"
>
<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>
</div>
</template>
<template #originalName="{ row }">
<el-icon v-if="row.dataType === 1">
<Folder />
</el-icon>
<el-icon v-else>
<Document />
</el-icon>
{{ row.originalName }}
</template>
<template #fileSize="{ row }">
{{ formatFileSize(row.fileSize) }}
</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>
</template>
</FileTree>
</div>
<ApprovalProcess v-model="processVisible" :flowId="currentRow?.cidFlowId" />
<knowledgeDetailModal
v-model="visible"
:detail="currentRow"
:folder="currentFolder"
:tableName="TABLE_NAME.SIMULATION_KNOWLEDGE"
@confirm="onConfirmFun"
/>
<folderModal
v-model="folderModalVisible"
:detail="currentFolder"
:mode="currentFolderMode"
@confirm="onFolderConfirmFun"
/>
<FilePreview v-model="previewVisible" :fileId="currentRow?.id" />
</div>
</template>
<script lang="ts" setup>
import { computed, h, 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 knowledgeDetailModal from './components/knowledgeDetailModal.vue';
import folderModal from './components/folderDetailModal.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 ApproveList from '@/components/common/approveList/index.vue';
import { useDict } from '@/utils/useDict';
import ApprovalProcess from '@/components/common/approvalProcess/index.vue';
import i18n from '@/utils/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';
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 = ref<any[]>([
{ title: '名称', key: 'fileName', inputMode: 'input' },
{
title: '上传人', key: 'uploadUserId', inputMode: 'userSelectMultiple',
},
]);
const folderModalVisible = 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 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);
} else {
ElMessage.error(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 || '',
});
const actionList = computed(() => {
return [
{
title: i18n?.global?.t('通用.预览'),
type: 'primary',
click: (row: any) => {
previewFileFun(row);
},
hide: (row: any) => {
return row.dataType !== 2;
},
},
{
title: i18n?.global?.t('通用.下载'),
type: 'primary',
click: (row: any) => {
downloadFileById(row.id);
},
hide: (row: any) => {
return row.dataType !== 2;
},
},
{
title: i18n?.global?.t('通用.编辑'),
type: 'primary',
click: (row: any) => {
editFileFun(row);
},
hide: (row: any) => {
return row.dataType !== 2 || row.approvalStatus === 'pending';
},
},
{
title: i18n?.global?.t('通用.删除'),
type: 'danger',
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 || '';
};
const resetFun = (data: object) => {
isSearching.value = false;
currentFolder.value = memoryFolder.value;
searchParams.value = cloneDeep(data);
searchParams.value.fileId = currentFolder.value?.id || '';
};
const refreshTableFun = async () => {
baseTableRef.value?.resetFun();
};
const onFolderConfirmFun = async (formData: any) => {
if (currentFolderMode.value === 'add') {
const req = {
dirType: DIR_TYPE.KNOWLEDGE,
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);
}
}
};
const visible = ref(false);
const currentRow = ref();
const openModalFun = () => {
visible.value = true;
currentRow.value = null;
};
const onConfirmFun = async (formData: any) => {
const req = {
...currentRow.value,
templateId: formData.templateId,
templateName: formData.templateName,
file: formData.file.raw,
fileName: formData.file.name,
size: formData.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,
};
let res: any;
if (currentRow.value && currentRow.value.id) {
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,
};
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知识库
},
});
});
}
}
if (res.code === 200) {
visible.value = false;
refreshTableFun();
ElMessage.success(res.message);
}
};
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) => {
const templateId = ref('');
const templateName = ref('');
ElMessageBox({
title: i18n?.global?.t('通用.审核模版'),
showCancelButton: true,
showConfirmButton: true,
confirmButtonText: i18n?.global?.t('通用.确定'),
cancelButtonText: i18n?.global?.t('通用.取消'),
message: () =>
h(ApproveList, {
modelValue: templateId.value,
'onChange': (val: any) => {
templateName.value = val.label;
templateId.value = val.value;
},
}),
}).then(async () => {
if (!templateId.value) {
ElMessage.error(i18n?.global?.t('通用.请选择审核模版'));
return;
}
const req = {
templateId: templateId.value,
templateName: templateName.value,
delFileId: row.id,
};
const res: any = await dataDelFileApi(req);
if (res.code === 200) {
refreshTableFun();
ElMessage.success(res.message);
}
});
};
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 = null;
}
});
const processVisible = ref(false);
const openProcessFun = (row: any) => {
currentRow.value = row;
processVisible.value = true;
};
</script>
<style lang="scss" scoped>
.gl-page-content-grey {
height: 100%;
.content {
height: 100%;
.options {
margin-bottom: var(--margin-small);
}
}
}
</style>