|
@@ -0,0 +1,530 @@
|
|
|
+<template>
|
|
|
+ <fs-page>
|
|
|
+ <el-row class="document-el-row">
|
|
|
+ <el-col :span="4">
|
|
|
+ <div class="document-box document-left-box">
|
|
|
+ <DocumentTreeCom
|
|
|
+ ref="documentTreeRef"
|
|
|
+ :treeData="documentTreeData"
|
|
|
+ @treeClick="handleTreeClick"
|
|
|
+ @updateDocument="handleUpdateDocument"
|
|
|
+ @deleteDocument="handleDeleteDocument"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="20">
|
|
|
+ <div class="document-box document-right-box">
|
|
|
+ <fs-crud ref="crudRef" v-bind="crudBinding">
|
|
|
+ <template #form_file="scope">
|
|
|
+ <!-- 只在编辑和添加模式下显示上传功能 -->
|
|
|
+ <template v-if="scope.mode !== 'view'">
|
|
|
+ <el-upload :action="getBaseURL() + 'api/system/minioupload/'"
|
|
|
+
|
|
|
+ :headers="{
|
|
|
+ Authorization: 'JWT ' + Session.get('token')
|
|
|
+ }"
|
|
|
+ :multiple="false"
|
|
|
+ :on-success="(response, file) => handleUploadSuccess(response, file, scope)"
|
|
|
+ :drag="false"
|
|
|
+ :show-file-list="false">
|
|
|
+ <el-button type="primary" icon="plus">上传</el-button>
|
|
|
+ </el-upload>
|
|
|
+ <div v-if="uploadedFile" class="uploaded-file-info mt-2">
|
|
|
+ <el-card shadow="hover" class="w-full">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div class="flex items-center">
|
|
|
+ <el-icon class="mr-2"><Document /></el-icon>
|
|
|
+ <span>{{ uploadedFile.name }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center">
|
|
|
+ <span class="text-gray-500 text-sm mr-4">{{ formatFileSize(uploadedFile.file_size) }}</span>
|
|
|
+ <el-button type="danger" link @click="handleRemoveFile(scope)">
|
|
|
+ <el-icon><Delete /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <!-- 查看模式下只显示文件名 -->
|
|
|
+ <template v-else>
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <span>{{ scope.form.fileName }}</span>
|
|
|
+ <el-button v-if="isPreviewable(scope.form.file_type)" type="primary" link @click="previewFile(scope.form)">
|
|
|
+ <el-icon class="mr-1"><View /></el-icon>预览
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </fs-crud>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-drawer v-model="drawerVisible" title="文档配置" direction="rtl" size="500px" :close-on-click-modal="false" :before-close="handleDrawerClose">
|
|
|
+ <DocumentFormCom
|
|
|
+ v-if="drawerVisible"
|
|
|
+ :initFormData="drawerFormData"
|
|
|
+ :cacheData="documentTreeCacheData"
|
|
|
+ :treeData="documentTreeData"
|
|
|
+ @drawerClose="handleDrawerClose"
|
|
|
+ />
|
|
|
+ </el-drawer>
|
|
|
+
|
|
|
+ <!-- 添加自定义查看详情对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="detailDialogVisible"
|
|
|
+ title="文档详情"
|
|
|
+ width="50%"
|
|
|
+ destroy-on-close
|
|
|
+ >
|
|
|
+ <div ref="printArea">
|
|
|
+ <el-descriptions :column="2" size="small" border>
|
|
|
+ <el-descriptions-item label-align="right" label="文档名称">{{ detailData.name || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label-align="right" label="文档类型">
|
|
|
+ {{ getDocTypeName(detailData.doc_type) || '-' }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label-align="right" label="文件分类">
|
|
|
+ {{ getCategoryName(detailData.category) || '-' }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label-align="right" label="所属项目">
|
|
|
+ {{ getProjectName(detailData.project) || '-' }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label-align="right" label="文档版本">{{ detailData.version || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label-align="right" label="文件类型">{{ detailData.file_type || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label-align="right" label="文件大小">{{ formatFileSize(detailData.file_size) || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label-align="right" label="状态">
|
|
|
+ <el-tag :type="detailData.status ? 'success' : 'info'">
|
|
|
+ {{ detailData.status ? '启用' : '禁用' }}
|
|
|
+ </el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label-align="right" label="文档描述" :span="2">{{ detailData.doc_desc || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label-align="right" label="更新时间">{{ detailData.update_datetime || '-' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button type="primary" @click="handlePrint">
|
|
|
+ <el-icon><Printer /></el-icon>打印
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="detailDialogVisible = false">关闭</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </fs-page>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup name="documentList">
|
|
|
+import { ref, onMounted, onBeforeUnmount } from 'vue';
|
|
|
+import { useFs } from '@fast-crud/fast-crud';
|
|
|
+import XEUtils from 'xe-utils';
|
|
|
+import { ElMessageBox, ElMessage } from 'element-plus';
|
|
|
+import { createCrudOptions } from './crud';
|
|
|
+import { Session } from '/@/utils/storage';
|
|
|
+import { getBaseURL } from '/@/utils/baseUrl';
|
|
|
+import { Document, Delete, View, Printer } from '@element-plus/icons-vue';
|
|
|
+import { successNotification } from '/@/utils/message';
|
|
|
+import DocumentTreeCom from './components/DocumentTreeCom/index.vue';
|
|
|
+import DocumentFormCom from './components/DocumentFormCom/index.vue';
|
|
|
+import { GetDocumentTree, DeleteDocumentCategory } from './api';
|
|
|
+import { useRouter } from 'vue-router';
|
|
|
+/* import { print } from '/@/utils/print'; */
|
|
|
+
|
|
|
+const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
|
|
|
+
|
|
|
+const router = useRouter();
|
|
|
+
|
|
|
+// 存储已上传文件信息
|
|
|
+const uploadedFile = ref(null);
|
|
|
+
|
|
|
+// 树形结构相关数据
|
|
|
+const documentTreeData = ref([]);
|
|
|
+const documentTreeCacheData = ref([]);
|
|
|
+const drawerVisible = ref(false);
|
|
|
+const drawerFormData = ref({});
|
|
|
+const documentTreeRef = ref(null);
|
|
|
+
|
|
|
+// 添加一个响应式变量来存储当前选中的分类ID
|
|
|
+const selectedCategoryId = ref('');
|
|
|
+
|
|
|
+// 添加详情对话框相关数据
|
|
|
+const detailDialogVisible = ref(false);
|
|
|
+const detailData = ref({
|
|
|
+ id: 0,
|
|
|
+ name: '',
|
|
|
+ doc_type: '',
|
|
|
+ category: '',
|
|
|
+ project: '',
|
|
|
+ version: '',
|
|
|
+ file_type: '',
|
|
|
+ file_size: 0,
|
|
|
+ status: true,
|
|
|
+ doc_desc: '',
|
|
|
+ update_datetime: '',
|
|
|
+ file_path: '',
|
|
|
+ file: ''
|
|
|
+});
|
|
|
+
|
|
|
+// 添加打印区域的ref
|
|
|
+const printArea = ref(null);
|
|
|
+
|
|
|
+// 获取文档树数据
|
|
|
+const getTreeData = () => {
|
|
|
+ GetDocumentTree({}).then((ret: any) => {
|
|
|
+ console.log('ret', ret);
|
|
|
+ const responseData = ret.data;
|
|
|
+ /* const result = XEUtils.toArrayTree(responseData, {
|
|
|
+ parentKey: 'parent',
|
|
|
+ children: 'children',
|
|
|
+ strict: true,
|
|
|
+ }); */
|
|
|
+ documentTreeData.value = responseData;
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 处理树节点点击
|
|
|
+const handleTreeClick = (record: any) => {
|
|
|
+ console.log('record', record);
|
|
|
+
|
|
|
+ // 保存当前选中的分类ID
|
|
|
+ selectedCategoryId.value = record?.id || '';
|
|
|
+
|
|
|
+ // 重置上传文件状态
|
|
|
+ uploadedFile.value = null;
|
|
|
+
|
|
|
+ // 构建搜索参数
|
|
|
+ const searchParams = {
|
|
|
+ form: {
|
|
|
+ job_id: selectedCategoryId.value
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 执行搜索
|
|
|
+ crudExpose.doSearch(searchParams);
|
|
|
+};
|
|
|
+
|
|
|
+// 处理文档分类的新增或编辑
|
|
|
+const handleUpdateDocument = (type: any, record: any) => {
|
|
|
+ if (type === 'update' && record) {
|
|
|
+ const parentData = documentTreeRef.value?.treeRef?.currentNode.parent.data || {};
|
|
|
+ documentTreeCacheData.value = [parentData];
|
|
|
+ drawerFormData.value = record;
|
|
|
+ }
|
|
|
+ drawerVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+// 关闭抽屉
|
|
|
+const handleDrawerClose = (type) => {
|
|
|
+ if (type === 'submit') {
|
|
|
+ getTreeData();
|
|
|
+ }
|
|
|
+ drawerVisible.value = false;
|
|
|
+ drawerFormData.value = {};
|
|
|
+};
|
|
|
+
|
|
|
+// 删除文档分类
|
|
|
+const handleDeleteDocument = (id: any, callback: any) => {
|
|
|
+ ElMessageBox.confirm('您确认删除该文档分类吗?', '温馨提示', {
|
|
|
+ confirmButtonText: '确认',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ }).then(async () => {
|
|
|
+ const res = await DeleteDocumentCategory(id);
|
|
|
+ callback();
|
|
|
+ if (res?.code === 2000) {
|
|
|
+ successNotification(res.msg);
|
|
|
+ getTreeData();
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 处理上传成功
|
|
|
+const handleUploadSuccess = (response: any, uploadFile: any, scope: any) => {
|
|
|
+ if (!response || !response.data) {
|
|
|
+ console.error('Upload response is invalid:', response);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const fileInfo = {
|
|
|
+ name: uploadFile.name,
|
|
|
+ file_path: response.data.bucket_url,
|
|
|
+ file_type: response.data.file_type,
|
|
|
+ file_size: uploadFile.size,
|
|
|
+ doc_type: scope.form.doc_type || '',
|
|
|
+ category: selectedCategoryId.value, // 使用当前选中的分类ID
|
|
|
+ status: true,
|
|
|
+ doc_desc: '',
|
|
|
+ version: '',
|
|
|
+ /* merchant: Session.get('merchant_info').merchant_id, */
|
|
|
+ file: response.data.bucket_url
|
|
|
+ };
|
|
|
+
|
|
|
+ // 更新表单数据
|
|
|
+ if (scope.form) {
|
|
|
+ Object.assign(scope.form, fileInfo);
|
|
|
+ } else {
|
|
|
+ console.error('Form reference is not available');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新已上传文件显示
|
|
|
+ uploadedFile.value = fileInfo;
|
|
|
+
|
|
|
+ // 刷新列表时传递category参数
|
|
|
+ crudExpose.doRefresh({
|
|
|
+ form: {
|
|
|
+ category: selectedCategoryId.value
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 处理文件删除
|
|
|
+const handleRemoveFile = (scope: any) => {
|
|
|
+ uploadedFile.value = null;
|
|
|
+ // 清空表单数据
|
|
|
+ if (crudRef.value?.form) {
|
|
|
+ const form = crudRef.value.form;
|
|
|
+ form.name = '';
|
|
|
+ form.file_path = '';
|
|
|
+ form.file_type = '';
|
|
|
+ form.file_size = '';
|
|
|
+ form.doc_type = '';
|
|
|
+ form.category = '';
|
|
|
+ form.doc_desc = '';
|
|
|
+ form.version = '';
|
|
|
+ form.file = '';
|
|
|
+ scope.value = '';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 格式化文件大小
|
|
|
+const formatFileSize = (bytes: number) => {
|
|
|
+ if (bytes === 0) return '0 B';
|
|
|
+ const k = 1024;
|
|
|
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
|
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
|
+};
|
|
|
+
|
|
|
+// 判断文件是否可预览
|
|
|
+const isPreviewable = (fileType: string) => {
|
|
|
+ if (!fileType) return false;
|
|
|
+
|
|
|
+ // 支持的文件类型
|
|
|
+ const supportedTypes = [
|
|
|
+ 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
|
+ 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'
|
|
|
+ ];
|
|
|
+
|
|
|
+ return supportedTypes.includes(fileType.toLowerCase());
|
|
|
+};
|
|
|
+
|
|
|
+// 预览文件
|
|
|
+const previewFile = (fileData: any) => {
|
|
|
+ if (!fileData || !fileData.file) {
|
|
|
+ ElMessageBox.alert('文件链接不存在', '预览失败', { type: 'error' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取文件类型
|
|
|
+ const fileType = fileData.file_type || '';
|
|
|
+
|
|
|
+ const previewUrl = `/#/preview?url=${encodeURIComponent(filePath)}&type=${fileType}`;
|
|
|
+ window.open(previewUrl, '_blank');
|
|
|
+};
|
|
|
+
|
|
|
+// 处理查看详情
|
|
|
+const handleViewDetail = async (event) => {
|
|
|
+ const row = event.detail || event;
|
|
|
+ try {
|
|
|
+ // 可以选择直接使用传入的行数据,或者重新请求完整数据
|
|
|
+ // 如果需要重新请求数据,可以添加一个 GetDocument API 方法
|
|
|
+ // const res = await GetDocument(row.id);
|
|
|
+ // detailData.value = res.data;
|
|
|
+
|
|
|
+ // 这里直接使用行数据
|
|
|
+ detailData.value = row;
|
|
|
+ detailDialogVisible.value = true;
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('获取文档详情失败');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 获取文档类型名称
|
|
|
+const getDocTypeName = (docTypeId) => {
|
|
|
+ if (!docTypeId) return '';
|
|
|
+ const docTypeDict = [
|
|
|
+ { value: 1, label: '合同文档' },
|
|
|
+ { value: 2, label: '技术文档' },
|
|
|
+ { value: 3, label: '操作手册' },
|
|
|
+ { value: 4, label: '维护记录' },
|
|
|
+ { value: 5, label: '其他文档' }
|
|
|
+ ];
|
|
|
+ const docType = docTypeDict.find(item => item.value === docTypeId);
|
|
|
+ return docType ? docType.label : docTypeId;
|
|
|
+};
|
|
|
+
|
|
|
+// 获取分类名称
|
|
|
+const getCategoryName = (categoryId) => {
|
|
|
+ if (!categoryId) return '';
|
|
|
+ const categoryDict = (window as any).__categoryDict || [];
|
|
|
+ const category = categoryDict.find(item => item.id === categoryId);
|
|
|
+ return category ? category.name : categoryId;
|
|
|
+};
|
|
|
+
|
|
|
+// 获取项目名称
|
|
|
+const getProjectName = (projectId) => {
|
|
|
+ if (!projectId) return '';
|
|
|
+ const projectDict = (window as any).__projectDict || [];
|
|
|
+ const project = projectDict.find(item => item.id === projectId);
|
|
|
+ return project ? project.name : projectId;
|
|
|
+};
|
|
|
+
|
|
|
+// 预览详情中的文件
|
|
|
+const previewDetailFile = (fileData) => {
|
|
|
+ if (!fileData || !fileData.file_path) {
|
|
|
+ ElMessage.error('文件路径无效');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const filePath = fileData.file_path;
|
|
|
+ const fileType = fileData.file_type;
|
|
|
+
|
|
|
+ // 支持的文件类型列表
|
|
|
+ const supportedTypes = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
|
|
+
|
|
|
+ if (!supportedTypes.includes(fileType)) {
|
|
|
+ ElMessage.warning(`暂不支持预览该文件类型: ${fileType}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用window.open在新窗口中打开预览页面
|
|
|
+ const previewUrl = `/#/preview?url=${encodeURIComponent(filePath)}&type=${fileType}`;
|
|
|
+ window.open(previewUrl, '_blank');
|
|
|
+};
|
|
|
+
|
|
|
+// 处理打印功能
|
|
|
+const handlePrint = () => {
|
|
|
+ /* print(printArea.value, {
|
|
|
+ title: `${detailData.value.name}文档详情`,
|
|
|
+ style: `
|
|
|
+ .el-descriptions { margin: 10px 0; }
|
|
|
+ .el-descriptions-item__label { font-weight: bold; }
|
|
|
+ .el-tag { padding: 2px 6px; }
|
|
|
+ @media print {
|
|
|
+ .dialog-footer { display: none; }
|
|
|
+ }
|
|
|
+ `
|
|
|
+ }); */
|
|
|
+};
|
|
|
+
|
|
|
+// 页面加载时获取数据
|
|
|
+onMounted(() => {
|
|
|
+ getTreeData();
|
|
|
+
|
|
|
+ // 先执行一次刷新,确保组件已完全初始化
|
|
|
+ crudExpose.doRefresh();
|
|
|
+
|
|
|
+ // 监听crud组件初始化完成
|
|
|
+ setTimeout(() => {
|
|
|
+ if (crudRef.value) {
|
|
|
+ // 确保搜索表单已初始化
|
|
|
+ if (!crudRef.value.searchForm) {
|
|
|
+ crudRef.value.searchForm = {};
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有默认选中的分类,设置搜索参数并执行搜索
|
|
|
+ if (selectedCategoryId.value) {
|
|
|
+ crudRef.value.searchForm.category = selectedCategoryId.value;
|
|
|
+ crudExpose.doSearch({
|
|
|
+ form: {
|
|
|
+ category: selectedCategoryId.value
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, 300);
|
|
|
+
|
|
|
+ // 添加全局事件监听器
|
|
|
+ window.addEventListener('viewDocumentDetail', handleViewDetail);
|
|
|
+});
|
|
|
+
|
|
|
+// 添加 onBeforeUnmount 钩子移除事件监听器
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ window.removeEventListener('viewDocumentDetail', handleViewDetail);
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.document-el-row {
|
|
|
+ height: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .el-col {
|
|
|
+ height: 100%;
|
|
|
+ padding: 10px 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.document-box {
|
|
|
+ height: 100%;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: var(--el-fill-color-blank);
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+.document-left-box {
|
|
|
+ position: relative;
|
|
|
+ border-radius: 0 8px 8px 0;
|
|
|
+ margin-right: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.document-right-box {
|
|
|
+ border-radius: 8px 0 0 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.mt-2 {
|
|
|
+ margin-top: 0.5rem;
|
|
|
+}
|
|
|
+.w-full {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.mr-2 {
|
|
|
+ margin-right: 0.5rem;
|
|
|
+}
|
|
|
+.mr-4 {
|
|
|
+ margin-right: 1rem;
|
|
|
+}
|
|
|
+.text-gray-500 {
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+.text-sm {
|
|
|
+ font-size: 0.875rem;
|
|
|
+}
|
|
|
+.flex {
|
|
|
+ display: flex;
|
|
|
+}
|
|
|
+.items-center {
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.justify-between {
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+/* 添加详情对话框样式 */
|
|
|
+.el-descriptions {
|
|
|
+ margin: 10px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+
|
|
|
+.mt-4 {
|
|
|
+ margin-top: 1rem;
|
|
|
+}
|
|
|
+</style>
|