|
@@ -1,1767 +0,0 @@
|
|
|
-<template>
|
|
|
|
|
- <div>
|
|
|
|
|
- <el-card shadow="never">
|
|
|
|
|
- <template #header>
|
|
|
|
|
- <div class="flex justify-between items-center">
|
|
|
|
|
- <span class="text-lg font-bold">{{ t('document.document.header.title') }}</span>
|
|
|
|
|
- <el-button type="primary" @click="handleBack">{{ t('document.document.header.backToList') }}</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
-
|
|
|
|
|
- <div class="content-wrapper">
|
|
|
|
|
- <div class="tree-container">
|
|
|
|
|
- <div class="tree-header">
|
|
|
|
|
- <el-button v-hasPermi="['document:folder:add']" type="primary" icon="Plus" size="small" @click="handleAddFolder">{{
|
|
|
|
|
- t('document.document.button.newFolder')
|
|
|
|
|
- }}</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-scrollbar class="tree-scrollbar">
|
|
|
|
|
- <el-tree v-loading="loading" :data="treeData" :props="treeProps" node-key="id" default-expand-all :expand-on-click-node="false">
|
|
|
|
|
- <template #default="{ node, data }">
|
|
|
|
|
- <span class="custom-tree-node">
|
|
|
|
|
- <el-icon>
|
|
|
|
|
- <Folder v-if="data.type === 0" />
|
|
|
|
|
- <Location v-else-if="data.type === 1" />
|
|
|
|
|
- <OfficeBuilding v-else-if="data.type === 2" />
|
|
|
|
|
- <Document v-else />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- <span class="node-label" @click="handleFolderClick(data)">{{ node.label }}</span>
|
|
|
|
|
- <span class="node-actions">
|
|
|
|
|
- <span class="menu-trigger" @click="toggleMenu($event, data)">
|
|
|
|
|
- <el-icon>
|
|
|
|
|
- <MoreFilled />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- </span>
|
|
|
|
|
- </span>
|
|
|
|
|
- </span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-tree>
|
|
|
|
|
- </el-scrollbar>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 一级菜单 -->
|
|
|
|
|
- <ul class="primary-menu" v-if="activeMenu !== null" :style="primaryMenuStyle">
|
|
|
|
|
- <li class="menu-item has-submenu" v-hasPermi="['document:folder:add']" @click.stop="toggleSubmenu($event)">
|
|
|
|
|
- <span>{{ t('document.document.menu.add') }}</span>
|
|
|
|
|
- <el-icon class="arrow-icon">
|
|
|
|
|
- <ArrowRight />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- </li>
|
|
|
|
|
- <li class="menu-item" v-hasPermi="['document:folder:edit']" @click="handleMenuItemClick('edit', currentMenuData)">
|
|
|
|
|
- <span>{{ t('document.document.menu.edit') }}</span>
|
|
|
|
|
- </li>
|
|
|
|
|
- <li class="menu-item" v-hasPermi="['document:folder:remove']" @click="handleMenuItemClick('delete', currentMenuData)">
|
|
|
|
|
- <span>{{ t('document.document.menu.delete') }}</span>
|
|
|
|
|
- </li>
|
|
|
|
|
- </ul>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 二级菜单 -->
|
|
|
|
|
- <ul class="secondary-menu" v-if="showSecondaryMenu" :style="secondaryMenuStyle">
|
|
|
|
|
- <!-- 国家或中心:显示中心和文件夹 -->
|
|
|
|
|
- <template v-if="currentMenuData && (currentMenuData.type === 1 || currentMenuData.type === 2)">
|
|
|
|
|
- <li class="menu-item" @click="handleMenuItemClick('add:2', currentMenuData)">
|
|
|
|
|
- <span>{{ t('document.document.menu.center') }}</span>
|
|
|
|
|
- </li>
|
|
|
|
|
- <li class="menu-item" @click="handleMenuItemClick('add:0', currentMenuData)">
|
|
|
|
|
- <span>{{ t('document.document.menu.folder') }}</span>
|
|
|
|
|
- </li>
|
|
|
|
|
- </template>
|
|
|
|
|
- <!-- 文件夹:只显示文件夹和文档 -->
|
|
|
|
|
- <template v-else-if="currentMenuData && currentMenuData.type === 0">
|
|
|
|
|
- <li class="menu-item" @click="handleMenuItemClick('add:0', currentMenuData)">
|
|
|
|
|
- <span>{{ t('document.document.menu.folder') }}</span>
|
|
|
|
|
- </li>
|
|
|
|
|
- <li class="menu-item" v-hasPermi="['document:document:add']" @click="handleMenuItemClick('add:document', currentMenuData)">
|
|
|
|
|
- <span>{{ t('document.document.menu.document') }}</span>
|
|
|
|
|
- </li>
|
|
|
|
|
- </template>
|
|
|
|
|
- </ul>
|
|
|
|
|
-
|
|
|
|
|
- <div class="content-container">
|
|
|
|
|
- <!-- 文档列表展示区域 -->
|
|
|
|
|
- <div v-if="selectedFolder" class="document-list-container">
|
|
|
|
|
- <!-- 搜索栏 -->
|
|
|
|
|
- <el-form :model="documentQueryParams" :inline="true" class="search-form">
|
|
|
|
|
- <el-form-item :label="t('document.document.documentList.fileName')">
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="documentQueryParams.name"
|
|
|
|
|
- :placeholder="t('document.document.documentList.fileNamePlaceholder')"
|
|
|
|
|
- clearable
|
|
|
|
|
- style="width: 240px"
|
|
|
|
|
- @keyup.enter="handleDocumentQuery"
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item>
|
|
|
|
|
- <el-button type="primary" icon="Search" @click="handleDocumentQuery">{{ t('document.document.button.search') }}</el-button>
|
|
|
|
|
- <el-button icon="Refresh" @click="handleDocumentReset">{{ t('document.document.button.reset') }}</el-button>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 文档列表 -->
|
|
|
|
|
- <el-table v-loading="documentLoading" :data="documentList" border style="margin-top: 10px">
|
|
|
|
|
- <el-table-column prop="id" width="55" align="center" :label="t('document.document.documentList.index')">
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- {{ scope.row.id }}
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="name" :label="t('document.document.documentList.name')" min-width="150" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column
|
|
|
|
|
- prop="specification"
|
|
|
|
|
- :label="t('document.document.documentList.specification')"
|
|
|
|
|
- min-width="120"
|
|
|
|
|
- show-overflow-tooltip
|
|
|
|
|
- >
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- <dict-tag v-if="scope.row.specification"
|
|
|
|
|
- :options="specificationDict"
|
|
|
|
|
- :value="scope.row.specification" />
|
|
|
|
|
- <span v-else>-</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="planType" :label="t('document.document.documentList.planDocumentType')" width="120" align="center">
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- <dict-tag v-if="scope.row.planType" :options="plan_document_type" :value="scope.row.planType" />
|
|
|
|
|
- <span v-else>-</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="status" :label="t('document.document.documentList.status')" width="120" align="center">
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- <el-tag v-if="scope.row.status === 0" size="small" type="info">{{
|
|
|
|
|
- t('document.document.documentList.statusOptions.unUpload')
|
|
|
|
|
- }}</el-tag>
|
|
|
|
|
- <el-tag v-else-if="scope.row.status === 1" size="small" type="warning">{{
|
|
|
|
|
- t('document.document.documentList.statusOptions.unAudit')
|
|
|
|
|
- }}</el-tag>
|
|
|
|
|
- <el-tag v-else-if="scope.row.status === 2" size="small" type="danger">{{
|
|
|
|
|
- t('document.document.documentList.statusOptions.auditReject')
|
|
|
|
|
- }}</el-tag>
|
|
|
|
|
- <el-tag v-else-if="scope.row.status === 3" size="small" type="warning">{{
|
|
|
|
|
- t('document.document.documentList.statusOptions.unFiling')
|
|
|
|
|
- }}</el-tag>
|
|
|
|
|
- <el-tag v-else-if="scope.row.status === 4" size="small" type="success">{{
|
|
|
|
|
- t('document.document.documentList.statusOptions.filing')
|
|
|
|
|
- }}</el-tag>
|
|
|
|
|
- <el-tag v-else-if="scope.row.status === 5" size="small" type="warning">{{
|
|
|
|
|
- t('document.document.documentList.statusOptions.unQualityControl')
|
|
|
|
|
- }}</el-tag>
|
|
|
|
|
- <el-tag v-else-if="scope.row.status === 6" size="small" type="success">{{
|
|
|
|
|
- t('document.document.documentList.statusOptions.qualityControlPass')
|
|
|
|
|
- }}</el-tag>
|
|
|
|
|
- <el-tag v-else-if="scope.row.status === 7" size="small" type="danger">{{
|
|
|
|
|
- t('document.document.documentList.statusOptions.qualityControlReject')
|
|
|
|
|
- }}</el-tag>
|
|
|
|
|
- <el-tag v-else size="small" type="default">-</el-tag>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="submitter" :label="t('document.document.documentList.submitter')" width="120" align="center" />
|
|
|
|
|
- <el-table-column prop="submitDeadline" :label="t('document.document.documentList.submitDeadline')" width="110" align="center">
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- <span v-if="scope.row.submitDeadline">{{ parseTime(scope.row.submitDeadline, '{y}-{m}-{d}') }}</span>
|
|
|
|
|
- <span v-else>-</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="submitTime" :label="t('document.document.documentList.submitTime')" width="160" align="center">
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- <span v-if="scope.row.submitTime">{{ parseTime(scope.row.submitTime) }}</span>
|
|
|
|
|
- <span v-else>-</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="url" :label="t('document.document.documentList.url')" min-width="200">
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- <div v-if="scope.row.fileName" class="file-name-cell">
|
|
|
|
|
- <svg-icon :icon-class="getFileIconClass(scope.row.fileName)" class="file-icon" :size="18" />
|
|
|
|
|
- <span class="file-name-text show-overflow-tooltip">{{ scope.row.fileName }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <span v-else>-</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="note" :label="t('document.document.documentList.note')" min-width="150" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column prop="createTime" :label="t('document.document.documentList.createTime')" width="160" align="center">
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- <span v-if="scope.row.createTime">{{ parseTime(scope.row.createTime) }}</span>
|
|
|
|
|
- <span v-else>-</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="updateTime" :label="t('document.document.documentList.updateTime')" width="160" align="center">
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- <span v-if="scope.row.updateTime">{{ parseTime(scope.row.updateTime) }}</span>
|
|
|
|
|
- <span v-else>-</span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <!-- 操作列 -->
|
|
|
|
|
- <el-table-column :label="t('document.document.documentList.action')" width="200" align="center" fixed="right">
|
|
|
|
|
- <template #default="scope">
|
|
|
|
|
- <!-- 审核按钮 -->
|
|
|
|
|
- <el-button
|
|
|
|
|
- v-if="scope.row.url && scope.row.status === 1"
|
|
|
|
|
- v-hasPermi="['document:document:audit']"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- link
|
|
|
|
|
- :icon="Select"
|
|
|
|
|
- @click="handleAuditClick(scope.row)"
|
|
|
|
|
- :title="t('document.document.button.audit')"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <!-- 递交按钮 -->
|
|
|
|
|
- <el-button
|
|
|
|
|
- v-if="(scope.row.status === 0 || scope.row.status === 2) && scope.row.submitterId === userStore.userId"
|
|
|
|
|
- v-hasPermi="['document:document:submit']"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- link
|
|
|
|
|
- :icon="Upload"
|
|
|
|
|
- @click="handleSubmit(scope.row)"
|
|
|
|
|
- :title="t('document.document.button.submit')"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <!-- 确认递交按钮 -->
|
|
|
|
|
- <el-button
|
|
|
|
|
- v-if="(scope.row.status === 0 || scope.row.status === 2) && scope.row.submitterId === userStore.userId"
|
|
|
|
|
- v-hasPermi="['document:document:confirmSubmit']"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- link
|
|
|
|
|
- icon="Delete"
|
|
|
|
|
- @click="handleConfirmSubmit(scope.row)"
|
|
|
|
|
- :title="t('document.document.button.confirmSubmit')"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <!-- 原有按钮 -->
|
|
|
|
|
- <el-button
|
|
|
|
|
- v-hasPermi="['document:document:mark']"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- link
|
|
|
|
|
- icon="Flag"
|
|
|
|
|
- @click="handleMark(scope.row)"
|
|
|
|
|
- :title="t('document.document.button.mark')"
|
|
|
|
|
- />
|
|
|
|
|
- <el-button
|
|
|
|
|
- v-if="scope.row.url"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- link
|
|
|
|
|
- icon="Download"
|
|
|
|
|
- @click="handleDownload(scope.row)"
|
|
|
|
|
- :title="t('document.document.button.download')"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <!-- 查看审核记录按钮 -->
|
|
|
|
|
- <el-button
|
|
|
|
|
- v-hasPermi="['document:document:logAudit']"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- link
|
|
|
|
|
- icon="DocumentCopy"
|
|
|
|
|
- @click="handleViewAuditLog(scope.row)"
|
|
|
|
|
- :title="t('document.document.button.viewAuditLog')"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <!-- 归档按钮 -->
|
|
|
|
|
- <el-button
|
|
|
|
|
- v-if="scope.row.status === 3"
|
|
|
|
|
- v-hasPermi="['document:document:filing']"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- link
|
|
|
|
|
- icon="UploadFilled"
|
|
|
|
|
- @click="handleArchive(scope.row)"
|
|
|
|
|
- :title="t('document.document.button.archive')"
|
|
|
|
|
- />
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- </el-table>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 分页 -->
|
|
|
|
|
- <pagination
|
|
|
|
|
- v-show="documentTotal > 0"
|
|
|
|
|
- v-model:page="documentQueryParams.pageNum"
|
|
|
|
|
- v-model:limit="documentQueryParams.pageSize"
|
|
|
|
|
- :total="documentTotal"
|
|
|
|
|
- @pagination="getDocumentList"
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 空状态 -->
|
|
|
|
|
- <el-empty v-else :description="t('document.document.empty.description')"> </el-empty>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-card>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 添加文件夹对话框 -->
|
|
|
|
|
- <el-dialog v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body>
|
|
|
|
|
- <el-form ref="folderFormRef" :model="form" :rules="rules" label-width="140px">
|
|
|
|
|
- <el-form-item :label="t('document.document.form.name')" prop="name">
|
|
|
|
|
- <el-input v-model="form.name" :placeholder="t('document.document.form.namePlaceholder')" clearable />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item :label="t('document.document.form.restrictionLevel')" prop="restrictionLevel">
|
|
|
|
|
- <el-radio-group v-model="isRestricted" @change="handleRestrictionChange">
|
|
|
|
|
- <el-radio :label="false">{{ t('document.document.form.noRestriction') }}</el-radio>
|
|
|
|
|
- <el-radio :label="true">{{ t('document.document.form.restricted') }}</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- <el-input-number
|
|
|
|
|
- v-if="isRestricted"
|
|
|
|
|
- v-model="restrictionLevelValue"
|
|
|
|
|
- :min="0"
|
|
|
|
|
- :max="10000"
|
|
|
|
|
- style="width: 100%; margin-top: 10px"
|
|
|
|
|
- :placeholder="t('document.document.form.restrictionLevelPlaceholder')"
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item :label="t('document.document.form.note')" prop="note">
|
|
|
|
|
- <el-input v-model="form.note" type="textarea" :rows="4" :placeholder="t('document.document.form.notePlaceholder')" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <template #footer>
|
|
|
|
|
- <div class="dialog-footer">
|
|
|
|
|
- <el-button :loading="buttonLoading" type="primary" @click="submitFolderForm">{{ t('document.document.button.submit') }}</el-button>
|
|
|
|
|
- <el-button @click="cancel">{{ t('document.document.button.cancel') }}</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 添加文档对话框 -->
|
|
|
|
|
- <el-dialog v-model="documentDialog.visible" :title="documentDialog.title" width="700px" append-to-body>
|
|
|
|
|
- <el-form ref="documentFormRef" :model="documentForm" :rules="documentRules" label-width="140px">
|
|
|
|
|
- <el-form-item
|
|
|
|
|
- :label="documentForm.type === 1 ? t('document.document.documentForm.planName') : t('document.document.documentForm.name')"
|
|
|
|
|
- prop="name"
|
|
|
|
|
- >
|
|
|
|
|
- <el-input v-model="documentForm.name" :placeholder="t('document.document.documentForm.namePlaceholder')" clearable />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
-
|
|
|
|
|
- <el-form-item :label="t('document.document.documentForm.type')" prop="type" v-if="hasAddPlanPermission">
|
|
|
|
|
- <el-radio-group v-model="documentForm.type" @change="handleDocumentTypeChange">
|
|
|
|
|
- <el-radio :label="0">{{ t('document.document.documentForm.normalDocument') }}</el-radio>
|
|
|
|
|
- <el-radio :label="1">{{ t('document.document.documentForm.planDocument') }}</el-radio>
|
|
|
|
|
- </el-radio-group>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
-
|
|
|
|
|
- <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.submitter')" prop="submitterId">
|
|
|
|
|
- <el-select
|
|
|
|
|
- v-model="documentForm.submitterId"
|
|
|
|
|
- filterable
|
|
|
|
|
- remote
|
|
|
|
|
- reserve-keyword
|
|
|
|
|
- :placeholder="t('document.document.documentForm.submitterPlaceholder')"
|
|
|
|
|
- :remote-method="searchSubmitters"
|
|
|
|
|
- :loading="submitterSearchLoading"
|
|
|
|
|
- style="width: 100%"
|
|
|
|
|
- >
|
|
|
|
|
- <el-option
|
|
|
|
|
- v-for="submitter in submitterOptions"
|
|
|
|
|
- :key="submitter.id"
|
|
|
|
|
- :label="`${submitter.name} / ${submitter.dept} --- ${submitter.phoneNumber}`"
|
|
|
|
|
- :value="submitter.id"
|
|
|
|
|
- />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
-
|
|
|
|
|
- <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.submitDeadline')" prop="submitDeadline">
|
|
|
|
|
- <el-date-picker
|
|
|
|
|
- v-model="documentForm.submitDeadline"
|
|
|
|
|
- type="date"
|
|
|
|
|
- value-format="YYYY-MM-DD"
|
|
|
|
|
- :placeholder="t('document.document.documentForm.submitDeadlinePlaceholder')"
|
|
|
|
|
- style="width: 100%"
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
-
|
|
|
|
|
- <el-form-item v-if="documentForm.type === 1" :label="t('document.document.documentForm.planType')" prop="planType">
|
|
|
|
|
- <el-select
|
|
|
|
|
- v-model="documentForm.planType"
|
|
|
|
|
- :placeholder="t('document.document.documentForm.planTypePlaceholder')"
|
|
|
|
|
- clearable
|
|
|
|
|
- style="width: 100%"
|
|
|
|
|
- >
|
|
|
|
|
- <el-option v-for="dict in plan_document_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
-
|
|
|
|
|
- <el-form-item :label="t('document.document.documentForm.file')" :prop="documentForm.type === 0 ? 'ossId' : ''">
|
|
|
|
|
- <fileUpload v-model="uploadedFileId" :limit="1" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
-
|
|
|
|
|
- <el-form-item v-if="documentForm.submitTime" :label="t('document.document.documentForm.submitTime')">
|
|
|
|
|
- <el-input v-model="documentForm.submitTime" disabled />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
-
|
|
|
|
|
- <el-form-item :label="t('document.document.documentForm.note')" prop="note">
|
|
|
|
|
- <el-input v-model="documentForm.note" type="textarea" :rows="4" :placeholder="t('document.document.documentForm.notePlaceholder')" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <template #footer>
|
|
|
|
|
- <div class="dialog-footer">
|
|
|
|
|
- <el-button :loading="documentButtonLoading" type="primary" @click="submitDocumentForm">{{
|
|
|
|
|
- t('document.document.button.submit')
|
|
|
|
|
- }}</el-button>
|
|
|
|
|
- <el-button @click="cancelDocument">{{ t('document.document.button.cancel') }}</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 标识文档对话框 -->
|
|
|
|
|
- <el-dialog v-model="markDialog.visible" :title="markDialog.title" width="500px" append-to-body>
|
|
|
|
|
- <el-form ref="markFormRef" :model="markForm" :rules="markRules" label-width="120px">
|
|
|
|
|
- <el-form-item :label="t('document.document.markForm.specification')" prop="type">
|
|
|
|
|
- <el-select v-model="markForm.type" :placeholder="t('document.document.markForm.specificationPlaceholder')" clearable style="width: 100%">
|
|
|
|
|
- <el-option v-for="dict in specificationDict" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <template #footer>
|
|
|
|
|
- <div class="dialog-footer">
|
|
|
|
|
- <el-button :loading="markButtonLoading" type="primary" @click="submitMarkForm">{{ t('document.document.button.submit') }}</el-button>
|
|
|
|
|
- <el-button @click="cancelMark">{{ t('document.document.button.cancel') }}</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 递交文档对话框 -->
|
|
|
|
|
- <el-dialog v-model="submitDialog.visible" :title="submitDialog.title" width="500px" append-to-body>
|
|
|
|
|
- <el-form ref="submitFormRef" :model="submitForm" :rules="submitRules" label-width="120px">
|
|
|
|
|
- <el-form-item :label="t('document.document.submitForm.file')" prop="ossId">
|
|
|
|
|
- <fileUpload v-model="submitForm.ossId" :limit="1" :action="'/common/resource/oss/upload'" />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <template #footer>
|
|
|
|
|
- <div class="dialog-footer">
|
|
|
|
|
- <el-button :loading="submitButtonLoading" type="primary" @click="submitSubmitForm">{{ t('document.document.button.submit') }}</el-button>
|
|
|
|
|
- <el-button @click="cancelSubmit">{{ t('document.document.button.cancel') }}</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dialog>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 文档审核记录对话框 -->
|
|
|
|
|
- <DocumentAuditLog v-model:visible="auditLogDialog.visible" :document-id="auditLogDialog.documentId" />
|
|
|
|
|
-
|
|
|
|
|
- <!-- 归档确认对话框 -->
|
|
|
|
|
- <ArchiveConfirmDialog v-model="archiveDialog.visible" :document="archiveDialog.currentDocument" @confirm="handleArchiveConfirm" />
|
|
|
|
|
-
|
|
|
|
|
- <!-- 审核文档对话框 -->
|
|
|
|
|
- <DocumentAuditDialog
|
|
|
|
|
- v-model="auditDialog.visible"
|
|
|
|
|
- :document="auditDialog.document"
|
|
|
|
|
- :title="t('document.document.dialog.auditDocument')"
|
|
|
|
|
- @success="getDocumentList"
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
-</template>
|
|
|
|
|
-
|
|
|
|
|
-<script setup lang="ts">
|
|
|
|
|
-import { ref, reactive, onMounted, onUnmounted, nextTick, getCurrentInstance, watch, computed } from 'vue';
|
|
|
|
|
-import { useI18n } from 'vue-i18n';
|
|
|
|
|
-import { listFolder, addFolder, delFolder, getFolder, updateFolder } from '@/api/document/folder';
|
|
|
|
|
-import { FolderListVO, FolderForm } from '@/api/document/folder/types';
|
|
|
|
|
-import {
|
|
|
|
|
- addDocument,
|
|
|
|
|
- listDocument,
|
|
|
|
|
- markDocument,
|
|
|
|
|
- auditDocument,
|
|
|
|
|
- submitDocument,
|
|
|
|
|
- confirmSubmit,
|
|
|
|
|
- listDocumentAuditLog,
|
|
|
|
|
- filingDocument
|
|
|
|
|
-} from '@/api/document/document';
|
|
|
|
|
-import {
|
|
|
|
|
- DocumentForm,
|
|
|
|
|
- DocumentQuery,
|
|
|
|
|
- DocumentVO,
|
|
|
|
|
- DocumentMarkForm,
|
|
|
|
|
- DocumentAuditForm,
|
|
|
|
|
- DocumentSubmitForm,
|
|
|
|
|
- DocumentAuditLogVO,
|
|
|
|
|
- DocumentAuditLogQuery
|
|
|
|
|
-} from '@/api/document/document/types';
|
|
|
|
|
-import { queryMemberNotInCenter } from '@/api/project/management';
|
|
|
|
|
-import { MemberNotInCenterVO, MemberNotInCenterQuery } from '@/api/project/management/types';
|
|
|
|
|
-import {
|
|
|
|
|
- Folder,
|
|
|
|
|
- Document,
|
|
|
|
|
- Edit,
|
|
|
|
|
- Delete,
|
|
|
|
|
- Plus,
|
|
|
|
|
- MoreFilled,
|
|
|
|
|
- Location,
|
|
|
|
|
- OfficeBuilding,
|
|
|
|
|
- ArrowRight,
|
|
|
|
|
- Download,
|
|
|
|
|
- Select,
|
|
|
|
|
- Grid,
|
|
|
|
|
- Monitor,
|
|
|
|
|
- Reading,
|
|
|
|
|
- Flag,
|
|
|
|
|
- Upload,
|
|
|
|
|
- UploadFilled
|
|
|
|
|
-} from '@element-plus/icons-vue';
|
|
|
|
|
-import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
|
|
-import type { FormInstance } from 'element-plus';
|
|
|
|
|
-import type { ComponentInternalInstance } from 'vue';
|
|
|
|
|
-import { useUserStore } from '@/store/modules/user';
|
|
|
|
|
-import { checkPermi } from '@/utils/permission';
|
|
|
|
|
-import fileUpload from '@/components/FileUpload/index.vue';
|
|
|
|
|
-import DocumentAuditLog from './document/components/DocumentAuditLog.vue';
|
|
|
|
|
-import ArchiveConfirmDialog from './document/components/ArchiveConfirmDialog.vue';
|
|
|
|
|
-import DocumentAuditDialog from './document/components/DocumentAuditDialog.vue';
|
|
|
|
|
-
|
|
|
|
|
-interface Props {
|
|
|
|
|
- projectId?: number | string;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const props = defineProps<Props>();
|
|
|
|
|
-
|
|
|
|
|
-const emit = defineEmits<{
|
|
|
|
|
- back: [];
|
|
|
|
|
-}>();
|
|
|
|
|
-
|
|
|
|
|
-const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
-const { t } = useI18n();
|
|
|
|
|
-const userStore = useUserStore();
|
|
|
|
|
-const { plan_document_type, center_file_specification, project_file_specification } = toRefs<any>(
|
|
|
|
|
- proxy?.useDict('plan_document_type', 'center_file_specification', 'project_file_specification')
|
|
|
|
|
-);
|
|
|
|
|
-
|
|
|
|
|
-// 数据定义
|
|
|
|
|
-const loading = ref(false);
|
|
|
|
|
-const buttonLoading = ref(false);
|
|
|
|
|
-const treeData = ref<FolderListVO[]>([]);
|
|
|
|
|
-const folderFormRef = ref<FormInstance>();
|
|
|
|
|
-const documentFormRef = ref<FormInstance>();
|
|
|
|
|
-const documentButtonLoading = ref(false);
|
|
|
|
|
-
|
|
|
|
|
-// 检查是否有计划文档添加权限
|
|
|
|
|
-const hasAddPlanPermission = computed(() => checkPermi(['document:document:addPlan']));
|
|
|
|
|
-
|
|
|
|
|
-// 当前用户信息
|
|
|
|
|
-const currentUserName = ref(userStore.nickname || '');
|
|
|
|
|
-
|
|
|
|
|
-// 审核记录对话框
|
|
|
|
|
-const auditLogDialog = reactive({
|
|
|
|
|
- visible: false,
|
|
|
|
|
- documentId: ''
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 归档对话框
|
|
|
|
|
-const archiveDialog = reactive({
|
|
|
|
|
- visible: false,
|
|
|
|
|
- currentDocument: null as DocumentVO | null
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 对话框
|
|
|
|
|
-const dialog = reactive({
|
|
|
|
|
- visible: false,
|
|
|
|
|
- title: '',
|
|
|
|
|
- isEdit: false
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 文档对话框
|
|
|
|
|
-const documentDialog = reactive({
|
|
|
|
|
- visible: false,
|
|
|
|
|
- title: ''
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 标识文档对话框
|
|
|
|
|
-const markDialog = reactive({
|
|
|
|
|
- visible: false,
|
|
|
|
|
- title: ''
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 标识表单ref
|
|
|
|
|
-const markFormRef = ref<FormInstance>();
|
|
|
|
|
-const markButtonLoading = ref(false);
|
|
|
|
|
-
|
|
|
|
|
-// 当前操作的节点
|
|
|
|
|
-const currentNode = ref<FolderListVO | null>(null);
|
|
|
|
|
-
|
|
|
|
|
-// 限制层级相关状态
|
|
|
|
|
-const isRestricted = ref(false); // 是否限制
|
|
|
|
|
-const restrictionLevelValue = ref(0); // 限制层级值
|
|
|
|
|
-
|
|
|
|
|
-// 表单初始数据
|
|
|
|
|
-const initFormData: FolderForm = {
|
|
|
|
|
- id: undefined,
|
|
|
|
|
- projectId: undefined,
|
|
|
|
|
- parentId: undefined,
|
|
|
|
|
- type: 0,
|
|
|
|
|
- name: '',
|
|
|
|
|
- status: 0,
|
|
|
|
|
- note: '',
|
|
|
|
|
- restrictionLevel: -1
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 表单数据
|
|
|
|
|
-const form = ref<FolderForm>({ ...initFormData });
|
|
|
|
|
-
|
|
|
|
|
-// 文档表单初始数据
|
|
|
|
|
-const initDocumentFormData: DocumentForm = {
|
|
|
|
|
- id: undefined,
|
|
|
|
|
- name: '',
|
|
|
|
|
- type: 0,
|
|
|
|
|
- submitterId: undefined,
|
|
|
|
|
- folderId: undefined,
|
|
|
|
|
- submitDeadline: undefined,
|
|
|
|
|
- planType: undefined,
|
|
|
|
|
- ossId: undefined,
|
|
|
|
|
- submitTime: undefined,
|
|
|
|
|
- projectId: props.projectId,
|
|
|
|
|
- note: ''
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 文件上传的ossId(字符串格式)
|
|
|
|
|
-const uploadedFileId = ref<string>('');
|
|
|
|
|
-
|
|
|
|
|
-// 文档表单数据
|
|
|
|
|
-const documentForm = ref<DocumentForm>({ ...initDocumentFormData });
|
|
|
|
|
-
|
|
|
|
|
-// 标识表单数据
|
|
|
|
|
-const markForm = ref<DocumentMarkForm>({
|
|
|
|
|
- id: 0,
|
|
|
|
|
- type: ''
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 递交文档对话框
|
|
|
|
|
-const submitDialog = reactive({
|
|
|
|
|
- visible: false,
|
|
|
|
|
- title: ''
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 递交表单ref
|
|
|
|
|
-const submitFormRef = ref<FormInstance>();
|
|
|
|
|
-const submitButtonLoading = ref(false);
|
|
|
|
|
-
|
|
|
|
|
-// 递交表单数据
|
|
|
|
|
-interface SubmitForm {
|
|
|
|
|
- ossId: string; // 文件OSS ID
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const submitForm = ref<SubmitForm>({
|
|
|
|
|
- ossId: ''
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 递交表单验证规则
|
|
|
|
|
-const submitRules = reactive({
|
|
|
|
|
- ossId: [
|
|
|
|
|
- {
|
|
|
|
|
- required: true,
|
|
|
|
|
- message: t('document.document.submitRule.fileRequired'),
|
|
|
|
|
- trigger: 'blur'
|
|
|
|
|
- }
|
|
|
|
|
- ]
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 当前操作的文档
|
|
|
|
|
-const currentDocument = ref<DocumentVO | null>(null);
|
|
|
|
|
-
|
|
|
|
|
-// 审核对话框状态
|
|
|
|
|
-const auditDialog = reactive({
|
|
|
|
|
- visible: false,
|
|
|
|
|
- document: null as DocumentVO | null
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 递交人搜索相关
|
|
|
|
|
-const submitterSearchLoading = ref(false);
|
|
|
|
|
-const submitterOptions = ref<MemberNotInCenterVO[]>([]);
|
|
|
|
|
-let submitterSearchTimer: NodeJS.Timeout | null = null;
|
|
|
|
|
-
|
|
|
|
|
-// ========== 文档列表相关 ==========
|
|
|
|
|
-// 文档列表数据
|
|
|
|
|
-const documentList = ref<DocumentVO[]>([]);
|
|
|
|
|
-const documentLoading = ref(false);
|
|
|
|
|
-const documentTotal = ref(0);
|
|
|
|
|
-
|
|
|
|
|
-// 当前选中的文件夹
|
|
|
|
|
-const selectedFolder = ref<FolderListVO | null>(null);
|
|
|
|
|
-
|
|
|
|
|
-// 文档查询参数
|
|
|
|
|
-const documentQueryParams = reactive<DocumentQuery>({
|
|
|
|
|
- pageNum: 1,
|
|
|
|
|
- pageSize: 10,
|
|
|
|
|
- name: '',
|
|
|
|
|
- folderId: undefined
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 表单验证规则
|
|
|
|
|
-const rules = {
|
|
|
|
|
- name: [{ required: true, message: t('document.document.rule.nameRequired'), trigger: 'blur' }],
|
|
|
|
|
- type: [{ required: true, message: t('document.document.rule.typeRequired'), trigger: 'change' }]
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 文档表单验证规则
|
|
|
|
|
-const documentRules = {
|
|
|
|
|
- name: [{ required: true, message: t('document.document.documentRule.nameRequired'), trigger: 'blur' }],
|
|
|
|
|
- submitterId: [{ required: true, message: t('document.document.documentRule.submitterRequired'), trigger: 'change' }],
|
|
|
|
|
- ossId: [{ required: true, message: t('document.document.documentRule.fileRequired'), trigger: 'change' }]
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 标识表单验证规则
|
|
|
|
|
-const markRules = {
|
|
|
|
|
- type: [{ required: true, message: t('document.document.markRule.typeRequired'), trigger: 'change' }]
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 树形组件配置
|
|
|
|
|
-const treeProps = {
|
|
|
|
|
- children: 'children',
|
|
|
|
|
- label: 'name'
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 根据当前文件夹获取对应的字典
|
|
|
|
|
-const specificationDict = computed(() => {
|
|
|
|
|
- if (!selectedFolder.value) return [];
|
|
|
|
|
-
|
|
|
|
|
- // 判断是否在中心底下
|
|
|
|
|
- const isUnderCenter = checkIfUnderCenter(selectedFolder.value.id);
|
|
|
|
|
-
|
|
|
|
|
- if (isUnderCenter) {
|
|
|
|
|
- return center_file_specification?.value || [];
|
|
|
|
|
- } else {
|
|
|
|
|
- return project_file_specification?.value || [];
|
|
|
|
|
- }
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 检查文件夹是否在中心底下
|
|
|
|
|
-const checkIfUnderCenter = (folderId: string | number): boolean => {
|
|
|
|
|
- // 从当前文件夹往上遍历,如果中间存在着中心类型的文件夹,那么他就是中心层级文件
|
|
|
|
|
- // 递归查找目标节点并收集从根到目标的路径
|
|
|
|
|
- const findPathToNode = (tree: FolderListVO[], targetId: string | number, path: FolderListVO[] = []): FolderListVO[] | null => {
|
|
|
|
|
- for (const node of tree) {
|
|
|
|
|
- const currentPath = [...path, node];
|
|
|
|
|
-
|
|
|
|
|
- if (node.id === targetId) {
|
|
|
|
|
- // 找到目标节点,返回路径
|
|
|
|
|
- return currentPath;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 在子节点中递归查找
|
|
|
|
|
- if (node.children && node.children.length > 0) {
|
|
|
|
|
- const result = findPathToNode(node.children, targetId, currentPath);
|
|
|
|
|
- if (result) {
|
|
|
|
|
- return result;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return null;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 获取从根到目标文件夹的路径
|
|
|
|
|
- const path = findPathToNode(treeData.value, folderId);
|
|
|
|
|
-
|
|
|
|
|
- if (!path) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 检查路径中是否存在type为1或2的节点(国家或中心)
|
|
|
|
|
- return path.some((node) => node.type === 1 || node.type === 2);
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 获取文件夹列表
|
|
|
|
|
-const getList = async () => {
|
|
|
|
|
- if (!props.projectId) {
|
|
|
|
|
- ElMessage.warning(t('document.document.message.projectIdNotExist'));
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- loading.value = true;
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await listFolder({ projectId: props.projectId } as any);
|
|
|
|
|
- treeData.value = res.data || [];
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- ElMessage.error(t('document.document.message.getFolderListFailed'));
|
|
|
|
|
- console.error(error);
|
|
|
|
|
- } finally {
|
|
|
|
|
- loading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 返回项目列表
|
|
|
|
|
-const handleBack = () => {
|
|
|
|
|
- emit('back');
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 表单重置
|
|
|
|
|
-const reset = () => {
|
|
|
|
|
- form.value = { ...initFormData };
|
|
|
|
|
- isRestricted.value = false;
|
|
|
|
|
- restrictionLevelValue.value = 0;
|
|
|
|
|
- folderFormRef.value?.resetFields();
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 处理限制状态变化
|
|
|
|
|
-const handleRestrictionChange = (value: boolean) => {
|
|
|
|
|
- if (value) {
|
|
|
|
|
- // 选择限制,使用restrictionLevelValue的值
|
|
|
|
|
- form.value.restrictionLevel = restrictionLevelValue.value;
|
|
|
|
|
- } else {
|
|
|
|
|
- // 选择不限制,设置为-1
|
|
|
|
|
- form.value.restrictionLevel = -1;
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 监听restrictionLevelValue变化,同步更新form.restrictionLevel
|
|
|
|
|
-watch(restrictionLevelValue, (newValue) => {
|
|
|
|
|
- if (isRestricted.value) {
|
|
|
|
|
- form.value.restrictionLevel = newValue;
|
|
|
|
|
- }
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 取消按钮
|
|
|
|
|
-const cancel = () => {
|
|
|
|
|
- reset();
|
|
|
|
|
- dialog.visible = false;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 新建文件夹(顶级)
|
|
|
|
|
-const handleAddFolder = () => {
|
|
|
|
|
- reset();
|
|
|
|
|
- currentNode.value = null;
|
|
|
|
|
- form.value.projectId = props.projectId;
|
|
|
|
|
- form.value.parentId = undefined;
|
|
|
|
|
- dialog.visible = true;
|
|
|
|
|
- dialog.title = t('document.document.dialog.addFolder');
|
|
|
|
|
- dialog.isEdit = false;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 新增子节点
|
|
|
|
|
-const handleAddChild = (data: FolderListVO) => {
|
|
|
|
|
- reset();
|
|
|
|
|
- currentNode.value = data;
|
|
|
|
|
- form.value.projectId = props.projectId;
|
|
|
|
|
- form.value.parentId = data.id;
|
|
|
|
|
- dialog.visible = true;
|
|
|
|
|
- dialog.title = t('document.document.dialog.addChild');
|
|
|
|
|
- dialog.isEdit = false;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 获取可选类型(根据父节点类型限制)
|
|
|
|
|
-const getAvailableTypes = () => {
|
|
|
|
|
- if (!currentNode.value) {
|
|
|
|
|
- // 顶级节点,可以选择所有类型
|
|
|
|
|
- return [
|
|
|
|
|
- { label: t('document.document.type.folder'), value: 0 },
|
|
|
|
|
- { label: t('document.document.type.country'), value: 1 },
|
|
|
|
|
- { label: t('document.document.type.center'), value: 2 }
|
|
|
|
|
- ];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const parentType = currentNode.value.type;
|
|
|
|
|
-
|
|
|
|
|
- if (parentType === 1 || parentType === 2) {
|
|
|
|
|
- // 父节点是国家或中心,子节点只能是中心或文件夹
|
|
|
|
|
- return [
|
|
|
|
|
- { label: t('document.document.type.folder'), value: 0 },
|
|
|
|
|
- { label: t('document.document.type.center'), value: 2 }
|
|
|
|
|
- ];
|
|
|
|
|
- } else {
|
|
|
|
|
- // 父节点是文件夹,子节点只能是文件夹
|
|
|
|
|
- return [{ label: t('document.document.type.folder'), value: 0 }];
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 下拉菜单命令处理
|
|
|
|
|
-const handleCommand = (command: string, data: FolderListVO) => {
|
|
|
|
|
- if (command.startsWith('add:')) {
|
|
|
|
|
- const cmdPart = command.split(':')[1];
|
|
|
|
|
- if (cmdPart === 'document') {
|
|
|
|
|
- handleAddDocument(data);
|
|
|
|
|
- } else {
|
|
|
|
|
- const type = parseInt(cmdPart);
|
|
|
|
|
- handleAddChildWithType(data, type);
|
|
|
|
|
- }
|
|
|
|
|
- } else if (command === 'edit') {
|
|
|
|
|
- handleEdit(data);
|
|
|
|
|
- } else if (command === 'delete') {
|
|
|
|
|
- handleDelete(data);
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 新增子节点(指定类型)
|
|
|
|
|
-const handleAddChildWithType = (data: FolderListVO, type: number) => {
|
|
|
|
|
- reset();
|
|
|
|
|
- currentNode.value = data;
|
|
|
|
|
- form.value.projectId = props.projectId;
|
|
|
|
|
- form.value.parentId = data.id;
|
|
|
|
|
- form.value.type = type;
|
|
|
|
|
- dialog.visible = true;
|
|
|
|
|
- const typeLabel =
|
|
|
|
|
- type === 0 ? t('document.document.type.folder') : type === 1 ? t('document.document.type.country') : t('document.document.type.center');
|
|
|
|
|
- dialog.title =
|
|
|
|
|
- type === 0
|
|
|
|
|
- ? t('document.document.dialog.addFolder')
|
|
|
|
|
- : type === 1
|
|
|
|
|
- ? t('document.document.dialog.addCountry')
|
|
|
|
|
- : t('document.document.dialog.addCenter');
|
|
|
|
|
- dialog.isEdit = false;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 提交表单
|
|
|
|
|
-const submitFolderForm = () => {
|
|
|
|
|
- folderFormRef.value?.validate(async (valid: boolean) => {
|
|
|
|
|
- if (valid) {
|
|
|
|
|
- // 如果是编辑,显示确认对话框
|
|
|
|
|
- if (dialog.isEdit) {
|
|
|
|
|
- try {
|
|
|
|
|
- const confirmMessage = `
|
|
|
|
|
- <div style="text-align: left;">
|
|
|
|
|
- <p><strong>${t('document.document.confirm.nameLabel')}</strong>${form.value.name}</p>
|
|
|
|
|
- <p><strong>${t('document.document.confirm.restrictionLevelLabel')}</strong>${form.value.restrictionLevel}</p>
|
|
|
|
|
- <p><strong>${t('document.document.confirm.noteLabel')}</strong>${form.value.note || t('document.document.confirm.noNote')}</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- `;
|
|
|
|
|
- await ElMessageBox.confirm(confirmMessage, t('document.document.dialog.confirmEdit'), {
|
|
|
|
|
- confirmButtonText: t('document.document.message.confirmButton'),
|
|
|
|
|
- cancelButtonText: t('document.document.message.cancelButton'),
|
|
|
|
|
- type: 'warning',
|
|
|
|
|
- dangerouslyUseHTMLString: true
|
|
|
|
|
- });
|
|
|
|
|
- } catch {
|
|
|
|
|
- return; // 用户取消
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- buttonLoading.value = true;
|
|
|
|
|
- try {
|
|
|
|
|
- if (dialog.isEdit) {
|
|
|
|
|
- await updateFolder(form.value);
|
|
|
|
|
- proxy?.$modal.msgSuccess(t('document.document.message.editSuccess'));
|
|
|
|
|
- } else {
|
|
|
|
|
- await addFolder(form.value);
|
|
|
|
|
- proxy?.$modal.msgSuccess(t('document.document.message.addSuccess'));
|
|
|
|
|
- }
|
|
|
|
|
- dialog.visible = false;
|
|
|
|
|
- await getList();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error(dialog.isEdit ? t('document.document.message.editFailed') : t('document.document.message.addFailed'), error);
|
|
|
|
|
- } finally {
|
|
|
|
|
- buttonLoading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 编辑
|
|
|
|
|
-const handleEdit = async (data: FolderListVO) => {
|
|
|
|
|
- reset();
|
|
|
|
|
- loading.value = true;
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await getFolder(data.id);
|
|
|
|
|
- Object.assign(form.value, res.data);
|
|
|
|
|
-
|
|
|
|
|
- // 设置限制层级状态
|
|
|
|
|
- if (form.value.restrictionLevel === -1) {
|
|
|
|
|
- isRestricted.value = false;
|
|
|
|
|
- restrictionLevelValue.value = 0;
|
|
|
|
|
- } else {
|
|
|
|
|
- isRestricted.value = true;
|
|
|
|
|
- restrictionLevelValue.value = form.value.restrictionLevel;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- currentNode.value = null; // 编辑时不限制类型
|
|
|
|
|
- dialog.visible = true;
|
|
|
|
|
- dialog.title = t('document.document.dialog.editFolder');
|
|
|
|
|
- dialog.isEdit = true;
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- ElMessage.error(t('document.document.message.getFolderInfoFailed'));
|
|
|
|
|
- console.error(error);
|
|
|
|
|
- } finally {
|
|
|
|
|
- loading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 删除
|
|
|
|
|
-const handleDelete = async (data: FolderListVO) => {
|
|
|
|
|
- // 检查是否有子节点
|
|
|
|
|
- if (data.children && data.children.length > 0) {
|
|
|
|
|
- ElMessage.warning(t('document.document.message.hasChildren'));
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- await ElMessageBox.confirm(t('document.document.message.deleteConfirm', { name: data.name }), t('document.document.message.deleteTitle'), {
|
|
|
|
|
- confirmButtonText: t('document.document.message.confirmButton'),
|
|
|
|
|
- cancelButtonText: t('document.document.message.cancelButton'),
|
|
|
|
|
- type: 'warning'
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- loading.value = true;
|
|
|
|
|
- await delFolder(data.id);
|
|
|
|
|
- ElMessage.success(t('document.document.message.deleteSuccess'));
|
|
|
|
|
- await getList();
|
|
|
|
|
- } catch (error: any) {
|
|
|
|
|
- // 用户取消删除或删除失败
|
|
|
|
|
- if (error !== 'cancel') {
|
|
|
|
|
- console.error(t('document.document.message.deleteFailed'), error);
|
|
|
|
|
- }
|
|
|
|
|
- } finally {
|
|
|
|
|
- loading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 菜单状态管理
|
|
|
|
|
-const activeMenu = ref<string | number | null>(null); // 当前激活的一级菜单
|
|
|
|
|
-const showSecondaryMenu = ref(false); // 是否显示二级菜单
|
|
|
|
|
-const primaryMenuStyle = ref<any>({}); // 一级菜单的样式(位置)
|
|
|
|
|
-const secondaryMenuStyle = ref<any>({}); // 二级菜单的样式(位置)
|
|
|
|
|
-const currentMenuData = ref<FolderListVO | null>(null); // 当前操作的菜单数据
|
|
|
|
|
-
|
|
|
|
|
-// 切换菜单显示
|
|
|
|
|
-const toggleMenu = (event: MouseEvent, data: FolderListVO) => {
|
|
|
|
|
- // 只处理点击事件,忽略其他事件
|
|
|
|
|
- if (event.type !== 'click') {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- event.stopPropagation();
|
|
|
|
|
- event.preventDefault();
|
|
|
|
|
-
|
|
|
|
|
- const trigger = event.currentTarget as HTMLElement;
|
|
|
|
|
- const rect = trigger.getBoundingClientRect();
|
|
|
|
|
-
|
|
|
|
|
- if (activeMenu.value === data.id) {
|
|
|
|
|
- // 如果点击的是同一个菜单,关闭它
|
|
|
|
|
- closeAllMenus();
|
|
|
|
|
- } else {
|
|
|
|
|
- // 关闭之前的菜单,打开新菜单
|
|
|
|
|
- // 计算一级菜单位置
|
|
|
|
|
- primaryMenuStyle.value = {
|
|
|
|
|
- left: `${rect.left}px`,
|
|
|
|
|
- top: `${rect.bottom + 2}px`
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- activeMenu.value = data.id;
|
|
|
|
|
- currentMenuData.value = data;
|
|
|
|
|
- showSecondaryMenu.value = false;
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 切换二级菜单显示
|
|
|
|
|
-const toggleSubmenu = (event: MouseEvent) => {
|
|
|
|
|
- // 只处理点击事件,忽略其他事件
|
|
|
|
|
- if (event.type !== 'click') {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- event.stopPropagation();
|
|
|
|
|
- event.preventDefault();
|
|
|
|
|
-
|
|
|
|
|
- const target = event.currentTarget as HTMLElement;
|
|
|
|
|
- const rect = target.getBoundingClientRect();
|
|
|
|
|
-
|
|
|
|
|
- if (showSecondaryMenu.value) {
|
|
|
|
|
- // 如果已经显示,则关闭
|
|
|
|
|
- showSecondaryMenu.value = false;
|
|
|
|
|
- } else {
|
|
|
|
|
- // 先设置位置,再显示(避免位置计算前就显示)
|
|
|
|
|
- secondaryMenuStyle.value = {
|
|
|
|
|
- left: `${rect.right + 5}px`,
|
|
|
|
|
- top: `${rect.top}px`
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 使用 nextTick 确保位置设置后再显示
|
|
|
|
|
- nextTick(() => {
|
|
|
|
|
- showSecondaryMenu.value = true;
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 处理菜单项点击
|
|
|
|
|
-const handleMenuItemClick = (command: string, data: FolderListVO | null) => {
|
|
|
|
|
- if (!data) return;
|
|
|
|
|
-
|
|
|
|
|
- // 执行命令
|
|
|
|
|
- handleCommand(command, data);
|
|
|
|
|
-
|
|
|
|
|
- // 关闭所有菜单
|
|
|
|
|
- closeAllMenus();
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// ========== 文档相关函数 ==========
|
|
|
|
|
-
|
|
|
|
|
-// 重置文档表单
|
|
|
|
|
-const resetDocumentForm = () => {
|
|
|
|
|
- documentForm.value = { ...initDocumentFormData };
|
|
|
|
|
- uploadedFileId.value = '';
|
|
|
|
|
- // 如果没有计划文档权限,默认为非计划文档
|
|
|
|
|
- if (!hasAddPlanPermission.value) {
|
|
|
|
|
- documentForm.value.type = 0;
|
|
|
|
|
- }
|
|
|
|
|
- documentForm.value.submitterId = userStore.userId;
|
|
|
|
|
- documentForm.value.projectId = props.projectId;
|
|
|
|
|
- currentUserName.value = userStore.nickname || '';
|
|
|
|
|
- submitterOptions.value = [];
|
|
|
|
|
- documentFormRef.value?.resetFields();
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 添加文档
|
|
|
|
|
-const handleAddDocument = (data: FolderListVO) => {
|
|
|
|
|
- resetDocumentForm();
|
|
|
|
|
- documentForm.value.folderId = data.id;
|
|
|
|
|
- documentDialog.visible = true;
|
|
|
|
|
- documentDialog.title = t('document.document.dialog.addDocument');
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 处理文档类型变化
|
|
|
|
|
-const handleDocumentTypeChange = (value: number) => {
|
|
|
|
|
- if (value === 0) {
|
|
|
|
|
- // 非计划文档,递交人为当前用户
|
|
|
|
|
- documentForm.value.submitterId = userStore.userId;
|
|
|
|
|
- documentForm.value.submitDeadline = undefined;
|
|
|
|
|
- documentForm.value.planType = undefined;
|
|
|
|
|
- } else {
|
|
|
|
|
- // 计划文档,清空递交人
|
|
|
|
|
- documentForm.value.submitterId = undefined;
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 搜索递交人
|
|
|
|
|
-const searchSubmitters = async (query: string) => {
|
|
|
|
|
- if (!query || query.trim() === '') {
|
|
|
|
|
- submitterOptions.value = [];
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 清除之前的定时器
|
|
|
|
|
- if (submitterSearchTimer) {
|
|
|
|
|
- clearTimeout(submitterSearchTimer);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 设置防抖
|
|
|
|
|
- submitterSearchTimer = setTimeout(async () => {
|
|
|
|
|
- submitterSearchLoading.value = true;
|
|
|
|
|
- try {
|
|
|
|
|
- const queryParams: MemberNotInCenterQuery = {
|
|
|
|
|
- pageNum: 1,
|
|
|
|
|
- pageSize: 10,
|
|
|
|
|
- projectId: props.projectId || 0,
|
|
|
|
|
- folderId: 0,
|
|
|
|
|
- name: query
|
|
|
|
|
- };
|
|
|
|
|
- const res = await queryMemberNotInCenter(queryParams);
|
|
|
|
|
- submitterOptions.value = res.rows || [];
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('Failed to search submitters:', error);
|
|
|
|
|
- ElMessage.error(t('document.document.message.searchSubmitterFailed'));
|
|
|
|
|
- } finally {
|
|
|
|
|
- submitterSearchLoading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
- }, 300);
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 监听文件上传变化
|
|
|
|
|
-watch(uploadedFileId, (newVal) => {
|
|
|
|
|
- if (newVal) {
|
|
|
|
|
- // 解析文件ID(可能是逗号分隔的字符串)
|
|
|
|
|
- const ids = newVal.split(',').filter((id) => id.trim());
|
|
|
|
|
- if (ids.length > 0) {
|
|
|
|
|
- documentForm.value.ossId = parseInt(ids[0]);
|
|
|
|
|
- // 自动设置递交时间为当前时间
|
|
|
|
|
- const now = new Date();
|
|
|
|
|
- documentForm.value.submitTime = proxy?.parseTime(now, '{y}-{m}-{d} {h}:{i}:{s}');
|
|
|
|
|
-
|
|
|
|
|
- // 对于非计划文档,自动设置递交人为当前用户
|
|
|
|
|
- if (documentForm.value.type === 0) {
|
|
|
|
|
- documentForm.value.submitterId = userStore.userId;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- documentForm.value.ossId = undefined;
|
|
|
|
|
- documentForm.value.submitTime = undefined;
|
|
|
|
|
- documentForm.value.submitterId = undefined;
|
|
|
|
|
- }
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 取消文档对话框
|
|
|
|
|
-const cancelDocument = () => {
|
|
|
|
|
- resetDocumentForm();
|
|
|
|
|
- documentDialog.visible = false;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 提交文档表单
|
|
|
|
|
-const submitDocumentForm = () => {
|
|
|
|
|
- documentFormRef.value?.validate(async (valid: boolean) => {
|
|
|
|
|
- if (valid) {
|
|
|
|
|
- documentButtonLoading.value = true;
|
|
|
|
|
- try {
|
|
|
|
|
- // 根据是否上传了文件设置status字段:上传了文件status为1,否则为0
|
|
|
|
|
- const hasUploadedFile = !!uploadedFileId.value;
|
|
|
|
|
-
|
|
|
|
|
- // 构建完整的请求数据,确保所有字段都存在(参考文件夹的实现)
|
|
|
|
|
- const submitData: DocumentForm = {
|
|
|
|
|
- id: documentForm.value.id || 0,
|
|
|
|
|
- name: documentForm.value.name || '',
|
|
|
|
|
- type: documentForm.value.type !== undefined ? documentForm.value.type : 0,
|
|
|
|
|
- submitterId: documentForm.value.submitterId || (documentForm.value.type === 0 ? userStore.userId : 0),
|
|
|
|
|
- folderId: documentForm.value.folderId || 0,
|
|
|
|
|
- submitDeadline: documentForm.value.submitDeadline || '',
|
|
|
|
|
- planType: documentForm.value.planType || '',
|
|
|
|
|
- ossId: hasUploadedFile ? uploadedFileId.value : null,
|
|
|
|
|
- submitTime: documentForm.value.submitTime || (hasUploadedFile ? new Date().toISOString() : ''),
|
|
|
|
|
- projectId: documentForm.value.projectId || props.projectId,
|
|
|
|
|
- status: hasUploadedFile ? 1 : 0,
|
|
|
|
|
- note: documentForm.value.note || ''
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- await addDocument(submitData);
|
|
|
|
|
- proxy?.$modal.msgSuccess(t('document.document.message.addDocumentSuccess'));
|
|
|
|
|
- documentDialog.visible = false;
|
|
|
|
|
- // 刷新文档列表
|
|
|
|
|
- await getDocumentList();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error(t('document.document.message.addDocumentFailed'), error);
|
|
|
|
|
- } finally {
|
|
|
|
|
- documentButtonLoading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// ========== 文档列表相关函数 ==========
|
|
|
|
|
-
|
|
|
|
|
-// 点击文件夹节点
|
|
|
|
|
-const handleFolderClick = (data: FolderListVO) => {
|
|
|
|
|
- // 只有文件夹类型(type=0)才显示文档列表
|
|
|
|
|
- if (data.type === 0) {
|
|
|
|
|
- selectedFolder.value = data;
|
|
|
|
|
- // 重置查询参数
|
|
|
|
|
- documentQueryParams.name = '';
|
|
|
|
|
- documentQueryParams.pageNum = 1;
|
|
|
|
|
- // 加载文档列表
|
|
|
|
|
- getDocumentList();
|
|
|
|
|
- } else {
|
|
|
|
|
- selectedFolder.value = null;
|
|
|
|
|
- documentList.value = [];
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 查询文档列表
|
|
|
|
|
-const getDocumentList = async () => {
|
|
|
|
|
- if (!selectedFolder.value) return;
|
|
|
|
|
-
|
|
|
|
|
- documentLoading.value = true;
|
|
|
|
|
- documentQueryParams.folderId = selectedFolder.value.id;
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- const res = await listDocument(documentQueryParams);
|
|
|
|
|
- documentList.value = res.rows || [];
|
|
|
|
|
- documentTotal.value = res.total || 0;
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('Failed to get document list:', error);
|
|
|
|
|
- ElMessage.error(t('document.document.message.getDocumentListFailed'));
|
|
|
|
|
- } finally {
|
|
|
|
|
- documentLoading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 搜索按钮
|
|
|
|
|
-const handleDocumentQuery = () => {
|
|
|
|
|
- documentQueryParams.pageNum = 1;
|
|
|
|
|
- getDocumentList();
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 重置按钮
|
|
|
|
|
-const handleDocumentReset = () => {
|
|
|
|
|
- documentQueryParams.name = '';
|
|
|
|
|
- documentQueryParams.pageNum = 1;
|
|
|
|
|
- getDocumentList();
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 查看审核记录
|
|
|
|
|
-const handleViewAuditLog = (row: DocumentVO) => {
|
|
|
|
|
- console.log('handleViewAuditLog called with row:', row);
|
|
|
|
|
- auditLogDialog.documentId = row.id;
|
|
|
|
|
- auditLogDialog.visible = true;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 审核按钮点击事件
|
|
|
|
|
-const handleAuditClick = (row: DocumentVO) => {
|
|
|
|
|
- auditDialog.document = row;
|
|
|
|
|
- auditDialog.visible = true;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 下载文档
|
|
|
|
|
-const handleDownload = (row: DocumentVO) => {
|
|
|
|
|
- if (!row.url) {
|
|
|
|
|
- ElMessage.warning(t('document.document.message.noFileToDownload'));
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 新建a标签下载文件
|
|
|
|
|
- const a = document.createElement('a');
|
|
|
|
|
- a.href = row.url;
|
|
|
|
|
- a.download = row.fileName || row.name || 'document';
|
|
|
|
|
- document.body.appendChild(a);
|
|
|
|
|
- a.click();
|
|
|
|
|
- document.body.removeChild(a);
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 递交文档
|
|
|
|
|
-const handleSubmit = (row: DocumentVO) => {
|
|
|
|
|
- currentDocument.value = row;
|
|
|
|
|
- submitForm.value = {
|
|
|
|
|
- ossId: ''
|
|
|
|
|
- };
|
|
|
|
|
- submitDialog.visible = true;
|
|
|
|
|
- submitDialog.title = t('document.document.dialog.submitDocument');
|
|
|
|
|
- // 重置表单验证
|
|
|
|
|
- nextTick(() => {
|
|
|
|
|
- submitFormRef.value?.clearValidate();
|
|
|
|
|
- });
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 取消递交
|
|
|
|
|
-const cancelSubmit = () => {
|
|
|
|
|
- submitDialog.visible = false;
|
|
|
|
|
- submitForm.value = {
|
|
|
|
|
- ossId: ''
|
|
|
|
|
- };
|
|
|
|
|
- currentDocument.value = null;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 提交递交表单
|
|
|
|
|
-const submitSubmitForm = () => {
|
|
|
|
|
- submitFormRef.value?.validate(async (valid: boolean) => {
|
|
|
|
|
- if (valid && currentDocument.value) {
|
|
|
|
|
- submitButtonLoading.value = true;
|
|
|
|
|
- try {
|
|
|
|
|
- const submitData: DocumentSubmitForm = {
|
|
|
|
|
- documentId: currentDocument.value.id,
|
|
|
|
|
- ossId: submitForm.value.ossId
|
|
|
|
|
- };
|
|
|
|
|
- await submitDocument(submitData);
|
|
|
|
|
- proxy?.$modal.msgSuccess(t('document.document.message.submitSuccess'));
|
|
|
|
|
- submitDialog.visible = false;
|
|
|
|
|
- // 刷新文档列表
|
|
|
|
|
- await getDocumentList();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error(t('document.document.message.submitFailed'), error);
|
|
|
|
|
- } finally {
|
|
|
|
|
- submitButtonLoading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 确认递交文档
|
|
|
|
|
-const handleConfirmSubmit = async (row: DocumentVO) => {
|
|
|
|
|
- try {
|
|
|
|
|
- await ElMessageBox.confirm('确认该文件已成功递交?', '操作确认', {
|
|
|
|
|
- confirmButtonText: '确认',
|
|
|
|
|
- cancelButtonText: '取消',
|
|
|
|
|
- type: 'warning'
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- await confirmSubmit(row.id);
|
|
|
|
|
- ElMessage.success('确认递交成功');
|
|
|
|
|
- // 刷新文档列表
|
|
|
|
|
- await getDocumentList();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- if (error !== 'cancel') {
|
|
|
|
|
- console.error('确认递交失败:', error);
|
|
|
|
|
- ElMessage.error('确认递交失败');
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 归档文档
|
|
|
|
|
-const handleArchive = (row: DocumentVO) => {
|
|
|
|
|
- archiveDialog.currentDocument = row;
|
|
|
|
|
- archiveDialog.visible = true;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 处理归档确认
|
|
|
|
|
-const handleArchiveConfirm = async (row: DocumentVO) => {
|
|
|
|
|
- try {
|
|
|
|
|
- // 调用归档API
|
|
|
|
|
- await filingDocument(row.id);
|
|
|
|
|
- ElMessage.success(t('document.document.message.archiveSuccess'));
|
|
|
|
|
- // 关闭对话框
|
|
|
|
|
- archiveDialog.visible = false;
|
|
|
|
|
- // 刷新文档列表
|
|
|
|
|
- await getDocumentList();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error(t('document.document.message.archiveFailed'), error);
|
|
|
|
|
- ElMessage.error(t('document.document.message.archiveFailed'));
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 标识文档
|
|
|
|
|
-const handleMark = (row: DocumentVO) => {
|
|
|
|
|
- currentDocument.value = row;
|
|
|
|
|
- markForm.value = {
|
|
|
|
|
- id: row.id,
|
|
|
|
|
- type: ''
|
|
|
|
|
- };
|
|
|
|
|
- markDialog.visible = true;
|
|
|
|
|
- markDialog.title = t('document.document.dialog.markDocument');
|
|
|
|
|
- // 重置表单验证
|
|
|
|
|
- nextTick(() => {
|
|
|
|
|
- markFormRef.value?.clearValidate();
|
|
|
|
|
- });
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 取消标识
|
|
|
|
|
-const cancelMark = () => {
|
|
|
|
|
- markDialog.visible = false;
|
|
|
|
|
- markForm.value = {
|
|
|
|
|
- id: 0,
|
|
|
|
|
- type: ''
|
|
|
|
|
- };
|
|
|
|
|
- currentDocument.value = null;
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 提交标识表单
|
|
|
|
|
-const submitMarkForm = () => {
|
|
|
|
|
- markFormRef.value?.validate(async (valid: boolean) => {
|
|
|
|
|
- if (valid) {
|
|
|
|
|
- markButtonLoading.value = true;
|
|
|
|
|
- try {
|
|
|
|
|
- await markDocument(markForm.value);
|
|
|
|
|
- proxy?.$modal.msgSuccess(t('document.document.message.markSuccess'));
|
|
|
|
|
- markDialog.visible = false;
|
|
|
|
|
- // 刷新文档列表
|
|
|
|
|
- await getDocumentList();
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error(t('document.document.message.markFailed'), error);
|
|
|
|
|
- } finally {
|
|
|
|
|
- markButtonLoading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// ========== 文件类型判断函数 ==========
|
|
|
|
|
-
|
|
|
|
|
-// 判断是否为Word文档
|
|
|
|
|
-const isWordFile = (fileName: string): boolean => {
|
|
|
|
|
- if (!fileName) return false;
|
|
|
|
|
- const lowerFileName = fileName.toLowerCase();
|
|
|
|
|
- return lowerFileName.endsWith('.doc') || lowerFileName.endsWith('.docx');
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 判断是否为Excel文档
|
|
|
|
|
-const isExcelFile = (fileName: string): boolean => {
|
|
|
|
|
- if (!fileName) return false;
|
|
|
|
|
- const lowerFileName = fileName.toLowerCase();
|
|
|
|
|
- return lowerFileName.endsWith('.xls') || lowerFileName.endsWith('.xlsx') || lowerFileName.endsWith('.csv');
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 判断是否为PPT文档
|
|
|
|
|
-const isPPTFile = (fileName: string): boolean => {
|
|
|
|
|
- if (!fileName) return false;
|
|
|
|
|
- const lowerFileName = fileName.toLowerCase();
|
|
|
|
|
- return lowerFileName.endsWith('.ppt') || lowerFileName.endsWith('.pptx');
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 判断是否为PDF文档
|
|
|
|
|
-const isPDFFile = (fileName: string): boolean => {
|
|
|
|
|
- if (!fileName) return false;
|
|
|
|
|
- return fileName.toLowerCase().endsWith('.pdf');
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 获取文件类型对应的图标类名
|
|
|
|
|
-const getFileIconClass = (fileName: string): string => {
|
|
|
|
|
- if (isWordFile(fileName)) {
|
|
|
|
|
- return 'document-word';
|
|
|
|
|
- } else if (isExcelFile(fileName)) {
|
|
|
|
|
- return 'document-excel';
|
|
|
|
|
- } else if (isPPTFile(fileName)) {
|
|
|
|
|
- return 'document-ppt';
|
|
|
|
|
- } else if (isPDFFile(fileName)) {
|
|
|
|
|
- return 'document-pdf';
|
|
|
|
|
- } else {
|
|
|
|
|
- return 'document-document';
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 关闭所有菜单
|
|
|
|
|
-const closeAllMenus = () => {
|
|
|
|
|
- showSecondaryMenu.value = false;
|
|
|
|
|
- activeMenu.value = null;
|
|
|
|
|
- currentMenuData.value = null;
|
|
|
|
|
- primaryMenuStyle.value = {};
|
|
|
|
|
- secondaryMenuStyle.value = {};
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 点击页面其他地方关闭菜单
|
|
|
|
|
-const handleClickOutside = (event: Event) => {
|
|
|
|
|
- // 如果没有激活的菜单,直接返回
|
|
|
|
|
- if (!activeMenu.value && !showSecondaryMenu.value) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const target = event.target as HTMLElement;
|
|
|
|
|
-
|
|
|
|
|
- // 检查点击是否在菜单内部或触发器上
|
|
|
|
|
- const isClickInsideMenu = target.closest('.primary-menu') || target.closest('.secondary-menu') || target.closest('.menu-trigger');
|
|
|
|
|
-
|
|
|
|
|
- // 如果点击在菜单外部,立即关闭所有菜单
|
|
|
|
|
- if (!isClickInsideMenu) {
|
|
|
|
|
- closeAllMenus();
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 处理滚动事件,滚动时关闭菜单
|
|
|
|
|
-const handleScroll = () => {
|
|
|
|
|
- if (activeMenu.value || showSecondaryMenu.value) {
|
|
|
|
|
- closeAllMenus();
|
|
|
|
|
- }
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-// 初始化
|
|
|
|
|
-onMounted(() => {
|
|
|
|
|
- getList();
|
|
|
|
|
- // 添加全局点击监听(捕获阶段)
|
|
|
|
|
- document.addEventListener('click', handleClickOutside, true);
|
|
|
|
|
- // 添加滚动监听
|
|
|
|
|
- document.addEventListener('scroll', handleScroll, true);
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-// 清理
|
|
|
|
|
-onUnmounted(() => {
|
|
|
|
|
- // 移除全局点击监听
|
|
|
|
|
- document.removeEventListener('click', handleClickOutside, true);
|
|
|
|
|
- // 移除滚动监听
|
|
|
|
|
- document.removeEventListener('scroll', handleScroll, true);
|
|
|
|
|
- // 清理菜单状态
|
|
|
|
|
- closeAllMenus();
|
|
|
|
|
- // 清理搜索定时器
|
|
|
|
|
- if (submitterSearchTimer) {
|
|
|
|
|
- clearTimeout(submitterSearchTimer);
|
|
|
|
|
- submitterSearchTimer = null;
|
|
|
|
|
- }
|
|
|
|
|
-});
|
|
|
|
|
-</script>
|
|
|
|
|
-
|
|
|
|
|
-<style scoped lang="scss">
|
|
|
|
|
-.flex {
|
|
|
|
|
- display: flex;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.justify-between {
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.items-center {
|
|
|
|
|
- align-items: center;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.text-lg {
|
|
|
|
|
- font-size: 1.125rem;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.font-bold {
|
|
|
|
|
- font-weight: 700;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.content-wrapper {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- height: calc(100vh - 250px);
|
|
|
|
|
- min-height: 500px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tree-container {
|
|
|
|
|
- width: 300px;
|
|
|
|
|
- border-right: 1px solid #e4e7ed;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tree-header {
|
|
|
|
|
- padding: 10px;
|
|
|
|
|
- border-bottom: 1px solid #e4e7ed;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tree-scrollbar {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
-
|
|
|
|
|
- :deep(.el-scrollbar__view) {
|
|
|
|
|
- padding: 10px;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.custom-tree-node {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- padding-right: 8px;
|
|
|
|
|
-
|
|
|
|
|
- .el-icon {
|
|
|
|
|
- margin-right: 8px;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .node-label {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- text-overflow: ellipsis;
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- color: var(--el-color-primary);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .node-actions {
|
|
|
|
|
- display: none;
|
|
|
|
|
- position: relative;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &:hover .node-actions {
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- gap: 4px;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.menu-trigger {
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- padding: 4px;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- transition:
|
|
|
|
|
- background-color 0.3s,
|
|
|
|
|
- color 0.3s;
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- background-color: #f5f7fa;
|
|
|
|
|
- color: var(--el-color-primary);
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.content-container {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- padding: 20px;
|
|
|
|
|
- overflow: auto;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.document-list-container {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
-
|
|
|
|
|
- .search-form {
|
|
|
|
|
- margin-bottom: 16px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .file-name-cell {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- padding: 0 8px;
|
|
|
|
|
-
|
|
|
|
|
- .file-icon {
|
|
|
|
|
- flex-shrink: 0;
|
|
|
|
|
- width: 18px;
|
|
|
|
|
- height: 18px;
|
|
|
|
|
- vertical-align: middle;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .file-name-text {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- text-align: left;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- text-overflow: ellipsis;
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .download-btn {
|
|
|
|
|
- flex-shrink: 0;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- transform: scale(1.1);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.detail-content {
|
|
|
|
|
- max-width: 800px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/* 一级菜单样式 */
|
|
|
|
|
-.primary-menu {
|
|
|
|
|
- position: fixed;
|
|
|
|
|
- min-width: 120px;
|
|
|
|
|
- background: #fff;
|
|
|
|
|
- border: 1px solid #e4e7ed;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
|
|
- padding: 5px 0;
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- list-style: none;
|
|
|
|
|
- z-index: 2000;
|
|
|
|
|
-
|
|
|
|
|
- .menu-item {
|
|
|
|
|
- padding: 8px 16px;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- color: #606266;
|
|
|
|
|
- transition:
|
|
|
|
|
- background-color 0.3s,
|
|
|
|
|
- color 0.3s;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- background-color: #f5f7fa;
|
|
|
|
|
- color: var(--el-color-primary);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &.has-submenu {
|
|
|
|
|
- .arrow-icon {
|
|
|
|
|
- margin-left: 8px;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/* 二级菜单样式 */
|
|
|
|
|
-.secondary-menu {
|
|
|
|
|
- position: fixed;
|
|
|
|
|
- min-width: 120px;
|
|
|
|
|
- background: #fff;
|
|
|
|
|
- border: 1px solid #e4e7ed;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
|
|
- padding: 5px 0;
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- list-style: none;
|
|
|
|
|
- z-index: 3000;
|
|
|
|
|
-
|
|
|
|
|
- .menu-item {
|
|
|
|
|
- padding: 8px 16px;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- color: #606266;
|
|
|
|
|
- transition:
|
|
|
|
|
- background-color 0.3s,
|
|
|
|
|
- color 0.3s;
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- background-color: #f5f7fa;
|
|
|
|
|
- color: var(--el-color-primary);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.show-overflow-tooltip {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- text-overflow: ellipsis;
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
- max-width: 100%;
|
|
|
|
|
- cursor: help;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.show-overflow-tooltip:hover {
|
|
|
|
|
- overflow: visible;
|
|
|
|
|
- white-space: normal;
|
|
|
|
|
- word-break: break-all;
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- background-color: rgba(0, 0, 0, 0.8);
|
|
|
|
|
- color: white;
|
|
|
|
|
- padding: 4px 8px;
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- z-index: 1000;
|
|
|
|
|
- max-width: 300px;
|
|
|
|
|
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
|
|
|
|
|
-}
|
|
|
|
|
-</style>
|
|
|