- 将文件列表表格包装在可滚动容器中,支持固定高度显示 - 新增选中文件元数据卡片组件,显示文件详细信息 - 调整左右分栏布局为拉伸对齐,优化高度计算 - 移除预览区域的文件元数据行,统一在卡片中显示 - 为共享文件和相关文件标签页添加独立的滚动样式 - 简化预览内容区域的flex属性配置
1152 lines
38 KiB
Vue
1152 lines
38 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<!-- 统计卡片 -->
|
||
<el-row :gutter="16" class="stat-row" v-if="activeTab === 'all'">
|
||
<el-col :span="6">
|
||
<div class="stat-card">
|
||
<div class="stat-num">{{ stats.totalFiles }}</div>
|
||
<div class="stat-label">文件总数</div>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<div class="stat-card public">
|
||
<div class="stat-num">{{ stats.publicFiles }}</div>
|
||
<div class="stat-label">公开文件</div>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<div class="stat-card private">
|
||
<div class="stat-num">{{ stats.privateFiles }}</div>
|
||
<div class="stat-label">私有文件</div>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<div class="stat-card">
|
||
<div class="stat-num">{{ stats.myFiles }}</div>
|
||
<div class="stat-label">我上传的</div>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- Tab 切换 -->
|
||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||
<el-tab-pane label="我的文件" name="my"></el-tab-pane>
|
||
<el-tab-pane label="共享文件" name="share"></el-tab-pane>
|
||
<el-tab-pane label="与我相关" name="related"></el-tab-pane>
|
||
</el-tabs>
|
||
|
||
<!-- 左右分栏布局 -->
|
||
<div class="file-layout" ref="layoutContainer">
|
||
<!-- 左侧:文件列表 -->
|
||
<div class="file-left" :style="{ width: activeTab === 'share' || activeTab === 'related' ? leftPanelWidth : '100%' }">
|
||
<!-- 搜索栏 -->
|
||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
||
<el-form-item label="文件名称" prop="fileName">
|
||
<el-input
|
||
v-model="queryParams.fileName"
|
||
placeholder="请输入文件名称"
|
||
clearable
|
||
style="width: 200px"
|
||
@keyup.enter.native="handleQuery"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="订单编号" prop="orderNo">
|
||
<el-input
|
||
v-model="queryParams.orderNo"
|
||
placeholder="请输入订单编号"
|
||
clearable
|
||
style="width: 200px"
|
||
@keyup.enter.native="handleQuery"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="所属部门" prop="dept">
|
||
<el-input
|
||
v-model="queryParams.dept"
|
||
placeholder="请输入部门"
|
||
clearable
|
||
style="width: 200px"
|
||
@keyup.enter.native="handleQuery"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="文件类型" prop="fileType">
|
||
<el-select
|
||
v-model="queryParams.fileType"
|
||
placeholder="请选择文件类型"
|
||
clearable
|
||
style="width: 200px"
|
||
>
|
||
<el-option
|
||
v-for="item in dict.type.sys_file_type"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="可见范围" prop="scopeType" v-if="activeTab !== 'share' && activeTab !== 'related'">
|
||
<el-select
|
||
v-model="queryParams.scopeType"
|
||
placeholder="请选择可见范围"
|
||
clearable
|
||
style="width: 200px"
|
||
>
|
||
<el-option label="公开" :value="1" />
|
||
<el-option label="私有" :value="2" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="上传时间">
|
||
<el-date-picker
|
||
v-model="dateRange"
|
||
style="width: 240px"
|
||
value-format="yyyy-MM-dd HH:mm:ss"
|
||
type="daterange"
|
||
range-separator="-"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
:default-time="['00:00:00', '23:59:59']"
|
||
></el-date-picker>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<!-- 操作按钮 -->
|
||
<el-row :gutter="10" class="mb8">
|
||
<el-col :span="1.5" v-if="activeTab === 'my' || activeTab === 'all'">
|
||
<el-button
|
||
type="primary"
|
||
plain
|
||
icon="el-icon-plus"
|
||
size="mini"
|
||
@click="handleAdd"
|
||
>上传文件</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5" v-if="activeTab === 'my' || activeTab === 'all'">
|
||
<el-button
|
||
type="danger"
|
||
plain
|
||
icon="el-icon-delete"
|
||
size="mini"
|
||
:disabled="multiple"
|
||
@click="handleDelete"
|
||
>删除</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button
|
||
type="warning"
|
||
plain
|
||
icon="el-icon-download"
|
||
size="mini"
|
||
@click="handleExport"
|
||
>导出</el-button>
|
||
</el-col>
|
||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||
</el-row>
|
||
|
||
<!-- 文件列表表格(固定高度可滚动) -->
|
||
<div :class="['file-table-wrapper', { scrolling: activeTab === 'share' || activeTab === 'related' }]">
|
||
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange" :highlight-current-row="activeTab === 'share' || activeTab === 'related'" @row-click="handleRowClick">
|
||
<el-table-column type="selection" width="55" align="center" v-if="activeTab === 'my' || activeTab === 'all'" />
|
||
<el-table-column label="文件名称" align="center" prop="fileName" :show-overflow-tooltip="true">
|
||
<template slot-scope="scope">
|
||
<el-link type="primary" @click="handlePreview(scope.row)">{{ scope.row.fileName }}</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="文件类型" align="center" prop="fileType" v-if="activeTab !== 'share' && activeTab !== 'related'">
|
||
<template slot-scope="scope">
|
||
<dict-tag :options="dict.type.sys_file_type" :value="scope.row.fileType"/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="文件大小" align="center" prop="fileSize" v-if="activeTab !== 'share' && activeTab !== 'related'">
|
||
<template slot-scope="scope">
|
||
{{ formatFileSize(scope.row.fileSize) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="订单编号" align="center" prop="orderNo" :show-overflow-tooltip="true" v-if="activeTab !== 'share' && activeTab !== 'related'" />
|
||
<el-table-column label="所属部门" align="center" prop="dept" :show-overflow-tooltip="true" v-if="activeTab !== 'share' && activeTab !== 'related'" />
|
||
<el-table-column label="可见范围" align="center" prop="scopeType" v-if="activeTab !== 'share' && activeTab !== 'related'">
|
||
<template slot-scope="scope">
|
||
<el-tag :type="scope.row.scopeType === 1 ? 'success' : 'warning'" size="small">
|
||
{{ scope.row.scopeType === 1 ? '公开' : '私有' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="上传人" align="center" prop="createBy" />
|
||
<el-table-column label="上传时间" align="center" prop="createTime" width="160">
|
||
<template slot-scope="scope">
|
||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="浏览次数" align="center" width="90" v-if="activeTab === 'share' || activeTab === 'related'">
|
||
<template slot-scope="scope">
|
||
<span>{{ scope.row.viewCount != null ? scope.row.viewCount : 0 }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" v-if="activeTab !== 'share' && activeTab !== 'related'" />
|
||
<el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width" v-if="activeTab !== 'share' && activeTab !== 'related'">
|
||
<template slot-scope="scope">
|
||
<el-button
|
||
size="mini"
|
||
type="text"
|
||
icon="el-icon-view"
|
||
@click="handlePreview(scope.row)"
|
||
>预览</el-button>
|
||
<el-button
|
||
v-if="canEdit(scope.row)"
|
||
size="mini"
|
||
type="text"
|
||
icon="el-icon-edit"
|
||
@click="handleUpdate(scope.row)"
|
||
>编辑</el-button>
|
||
<el-button
|
||
v-if="canEdit(scope.row)"
|
||
size="mini"
|
||
type="text"
|
||
icon="el-icon-delete"
|
||
@click="handleDelete(scope.row)"
|
||
>删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</KLPTable>
|
||
|
||
<pagination
|
||
v-show="total>0"
|
||
:total="total"
|
||
:page.sync="queryParams.pageNum"
|
||
:limit.sync="queryParams.pageSize"
|
||
@pagination="getList"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 选中文件元数据卡片(仅共享文件 / 与我相关 tab) -->
|
||
<div class="file-meta-card" v-if="selectedFile && (activeTab === 'share' || activeTab === 'related')">
|
||
<el-descriptions :column="2" border size="small">
|
||
<el-descriptions-item label="文件名称">{{ selectedFile.fileName }}</el-descriptions-item>
|
||
<el-descriptions-item label="文件类型">
|
||
<dict-tag :options="dict.type.sys_file_type" :value="selectedFile.fileType"/>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="文件大小">{{ formatFileSize(selectedFile.fileSize) }}</el-descriptions-item>
|
||
<el-descriptions-item label="文件后缀">{{ selectedFile.suffix || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="浏览次数">{{ selectedFile.viewCount != null ? selectedFile.viewCount : 0 }} 次</el-descriptions-item>
|
||
<el-descriptions-item label="订单编号">{{ selectedFile.orderNo || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="所属部门">{{ selectedFile.dept || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="可见范围">
|
||
<el-tag :type="selectedFile.scopeType === 1 ? 'success' : 'warning'" size="small">
|
||
{{ selectedFile.scopeType === 1 ? '公开' : '私有' }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="上传人">{{ selectedFile.createBy }}</el-descriptions-item>
|
||
<el-descriptions-item label="上传时间">{{ parseTime(selectedFile.createTime) }}</el-descriptions-item>
|
||
<el-descriptions-item label="备注" :span="2">{{ selectedFile.remark || '-' }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 拖拽分隔条(共享文件 / 与我相关 tab) -->
|
||
<div class="resize-handle" v-if="activeTab === 'share' || activeTab === 'related'" @mousedown="startResize" :class="{ dragging: isDragging }"></div>
|
||
|
||
<!-- 右侧:文件预览(共享文件 / 与我相关 tab) -->
|
||
<div class="file-right" v-if="activeTab === 'share' || activeTab === 'related'">
|
||
<div class="preview-panel">
|
||
<div v-if="!selectedFile" class="preview-empty">
|
||
<el-empty description="请选择左侧文件进行预览" />
|
||
</div>
|
||
<template v-else>
|
||
<div class="preview-header">
|
||
<span class="preview-filename" :title="selectedFile.fileName">{{ selectedFile.fileName }}</span>
|
||
<div>
|
||
<el-button size="mini" type="primary" icon="el-icon-download" @click="downloadFile(selectedFile)">下载</el-button>
|
||
<el-button v-if="canEdit(selectedFile)" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(selectedFile)">编辑</el-button>
|
||
<el-button v-if="canEdit(selectedFile)" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(selectedFile)">删除</el-button>
|
||
</div>
|
||
</div>
|
||
<div class="preview-comment">
|
||
<div class="comment-bar" @click="commentExpanded = !commentExpanded">
|
||
<span><i class="el-icon-chat-dot-round"></i> 评论 ({{ comments.length }})</span>
|
||
<span class="comment-toggle">{{ commentExpanded ? '收起' : '展开' }} <i :class="commentExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i></span>
|
||
</div>
|
||
<div v-show="commentExpanded" class="comment-body">
|
||
<div v-if="commentLoading" class="comment-hint"><i class="el-icon-loading"></i> 加载中...</div>
|
||
<div v-else-if="comments.length === 0" class="comment-hint comment-empty">暂无评论</div>
|
||
<div v-else class="comment-list">
|
||
<div v-for="item in comments" :key="item.commentId" class="comment-item">
|
||
<div class="comment-meta">
|
||
<span class="comment-dept">{{ item.dept }}</span>
|
||
<span class="comment-user">{{ item.createBy }}</span>
|
||
<span class="comment-time">{{ parseTime(item.createTime) }}</span>
|
||
</div>
|
||
<div class="comment-text">{{ item.content }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="comment-input-area">
|
||
<el-input
|
||
v-model="commentInput"
|
||
type="textarea"
|
||
:rows="2"
|
||
placeholder="输入评论..."
|
||
resize="none"
|
||
/>
|
||
<el-button type="primary" size="mini" @click="handleAddComment" :disabled="!commentInput.trim()" style="margin-top: 6px;">发送</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="preview-content">
|
||
<ImagePreview v-if="fileTypeCategory === 'image'" :src="selectedFile.filePath" />
|
||
<PdfPreview v-else-if="fileTypeCategory === 'pdf'" :src="selectedFile.filePath" />
|
||
<DocxPreview v-else-if="fileTypeCategory === 'docx'" :src="selectedFile.filePath" />
|
||
<XlsxPreview v-else-if="fileTypeCategory === 'xlsx'" :src="selectedFile.filePath" />
|
||
<XlsPreview v-else-if="fileTypeCategory === 'xls'" :src="selectedFile.filePath" />
|
||
<div v-else class="preview-not-supported">
|
||
<el-empty description="暂不支持预览此文件类型" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 上传/编辑对话框 -->
|
||
<el-dialog :title="dialogTitle" :visible.sync="open" width="650px" append-to-body @close="handleDialogClose">
|
||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||
<el-form-item label="文件名称" prop="fileName">
|
||
<el-input v-model="form.fileName" placeholder="请输入文件名称" :disabled="isEdit" />
|
||
</el-form-item>
|
||
<el-form-item label="上传文件" v-if="!isEdit">
|
||
<el-upload
|
||
ref="upload"
|
||
:action="uploadUrl"
|
||
:headers="uploadHeaders"
|
||
:file-list="uploadFileList"
|
||
:before-upload="handleBeforeUpload"
|
||
:on-success="handleUploadSuccess"
|
||
:on-error="handleUploadError"
|
||
:auto-upload="false"
|
||
:limit="1"
|
||
class="upload-demo"
|
||
drag
|
||
>
|
||
<i class="el-icon-upload"></i>
|
||
<div class="el-upload__text">将文件拖到此处,或<em>点击选取</em></div>
|
||
</el-upload>
|
||
</el-form-item>
|
||
<el-form-item label="订单编号" prop="orderNo">
|
||
<el-input v-model="form.orderNo" placeholder="请输入订单编号" />
|
||
</el-form-item>
|
||
<el-form-item label="所属部门" prop="dept">
|
||
<el-input v-model="form.dept" placeholder="请输入所属部门" />
|
||
</el-form-item>
|
||
<el-form-item label="文件类型" prop="fileType">
|
||
<el-select v-model="form.fileType" placeholder="请选择文件类型" style="width: 100%">
|
||
<el-option
|
||
v-for="item in dict.type.sys_file_type"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="可见范围" prop="scopeType">
|
||
<el-radio-group v-model="form.scopeType" @change="handleScopeChange">
|
||
<el-radio :label="1">公开(所有人可见)</el-radio>
|
||
<el-radio :label="2">私有(指定用户可见)</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="可见用户" v-if="form.scopeType === 2">
|
||
<user-select
|
||
v-model="form.visibleUsers"
|
||
:multiple="true"
|
||
placeholder="请选择可见用户"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" :rows="3" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||
<el-button @click="cancel">取 消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 文件预览对话框(仅非共享文件 tab 使用) -->
|
||
<el-dialog :title="previewTitle" :visible.sync="previewVisible" width="80%" append-to-body>
|
||
<div v-if="previewFile" class="file-preview-container">
|
||
<el-descriptions :column="2" border>
|
||
<el-descriptions-item label="文件名称">{{ previewFile.fileName }}</el-descriptions-item>
|
||
<el-descriptions-item label="文件类型">
|
||
<dict-tag :options="dict.type.sys_file_type" :value="previewFile.fileType"/>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="文件大小">{{ formatFileSize(previewFile.fileSize) }}</el-descriptions-item>
|
||
<el-descriptions-item label="文件后缀">{{ previewFile.suffix }}</el-descriptions-item>
|
||
<el-descriptions-item label="订单编号">{{ previewFile.orderNo || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="所属部门">{{ previewFile.dept || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="可见范围">
|
||
<el-tag :type="previewFile.scopeType === 1 ? 'success' : 'warning'" size="small">
|
||
{{ previewFile.scopeType === 1 ? '公开' : '私有' }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="上传人">{{ previewFile.createBy }}</el-descriptions-item>
|
||
<el-descriptions-item label="上传时间">{{ parseTime(previewFile.createTime) }}</el-descriptions-item>
|
||
<el-descriptions-item label="备注" :span="2">{{ previewFile.remark || '-' }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
<div v-if="previewFile.filePath" style="margin-top: 16px;">
|
||
<el-button type="primary" size="small" @click="downloadFile(previewFile)">下载文件</el-button>
|
||
</div>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { listFile, getFile, addFile, updateFile, delFile, exportFile, listVisibleUser, addVisibleUser, delVisibleUser, listVisibleUserByFileId, listRelatedToMe, incrementView } from '@/api/system/file'
|
||
import { listFileComment, addFileComment } from '@/api/system/fileComment'
|
||
import { getToken } from '@/utils/auth'
|
||
import UserSelect from '@/components/KLPService/UserSelect/index'
|
||
import ImagePreview from '@/components/FilePreview/preview/image/index.vue'
|
||
import PdfPreview from '@/components/FilePreview/preview/pdf/index.vue'
|
||
import DocxPreview from '@/components/FilePreview/preview/docx/index.vue'
|
||
import XlsxPreview from '@/components/FilePreview/preview/xlsx/index.vue'
|
||
import XlsPreview from '@/components/FilePreview/preview/xls/index.vue'
|
||
|
||
export default {
|
||
name: 'SysFile',
|
||
dicts: ['sys_file_type'],
|
||
components: {
|
||
UserSelect,
|
||
ImagePreview,
|
||
PdfPreview,
|
||
DocxPreview,
|
||
XlsxPreview,
|
||
XlsPreview
|
||
},
|
||
data() {
|
||
return {
|
||
// 当前激活tab
|
||
activeTab: 'my',
|
||
// 遮罩层
|
||
loading: false,
|
||
// 选中数组
|
||
ids: [],
|
||
// 非单个禁用
|
||
single: true,
|
||
// 非多个禁用
|
||
multiple: true,
|
||
// 显示搜索条件
|
||
showSearch: true,
|
||
// 总条数
|
||
total: 0,
|
||
// 文件列表
|
||
fileList: [],
|
||
// 弹出层标题
|
||
dialogTitle: '',
|
||
// 是否显示弹出层
|
||
open: false,
|
||
// 是否编辑
|
||
isEdit: false,
|
||
// 日期范围
|
||
dateRange: [],
|
||
// 查询参数
|
||
queryParams: {
|
||
pageNum: 1,
|
||
pageSize: 20,
|
||
fileName: undefined,
|
||
orderNo: undefined,
|
||
dept: undefined,
|
||
fileType: undefined,
|
||
scopeType: undefined
|
||
},
|
||
// 表单
|
||
form: {
|
||
fileId: undefined,
|
||
fileName: undefined,
|
||
filePath: undefined,
|
||
fileSize: undefined,
|
||
suffix: undefined,
|
||
orderNo: undefined,
|
||
dept: undefined,
|
||
fileType: undefined,
|
||
scopeType: 1,
|
||
visibleUsers: [],
|
||
remark: undefined
|
||
},
|
||
// 可见用户原始数据(编辑时回显)
|
||
originalVisibleUsers: [],
|
||
// 表单校验
|
||
rules: {
|
||
fileName: [
|
||
{ required: true, message: '文件名称不能为空', trigger: 'blur' }
|
||
],
|
||
fileType: [
|
||
{ required: true, message: '请选择文件类型', trigger: 'change' }
|
||
]
|
||
},
|
||
// 统计
|
||
stats: {
|
||
totalFiles: 0,
|
||
publicFiles: 0,
|
||
privateFiles: 0,
|
||
myFiles: 0
|
||
},
|
||
// 上传相关
|
||
uploadUrl: process.env.VUE_APP_BASE_API + '/system/oss/upload',
|
||
uploadHeaders: {
|
||
Authorization: 'Bearer ' + getToken()
|
||
},
|
||
uploadFileList: [],
|
||
// 预览对话框(非共享文件 tab)
|
||
previewVisible: false,
|
||
previewTitle: '',
|
||
previewFile: null,
|
||
// 选中的文件(共享文件 tab 右侧预览)
|
||
selectedFile: null,
|
||
// 评论
|
||
comments: [],
|
||
commentExpanded: false,
|
||
commentInput: '',
|
||
commentLoading: false,
|
||
// 拖拽调节宽度(共享文件 tab)
|
||
leftPanelWidth: '40%',
|
||
isDragging: false,
|
||
startX: 0,
|
||
startLeftWidth: 60
|
||
}
|
||
},
|
||
computed: {
|
||
/** 根据文件后缀分类,决定右侧用哪个预览组件 */
|
||
fileTypeCategory() {
|
||
if (!this.selectedFile) return null
|
||
const raw = this.selectedFile.suffix || this.selectedFile.fileName || ''
|
||
const ext = (raw.includes('.') ? raw.split('.').pop() : raw).toLowerCase()
|
||
if (['png', 'jpg', 'jpeg', 'bmp', 'webp'].includes(ext)) return 'image'
|
||
if (ext === 'pdf') return 'pdf'
|
||
if (ext === 'docx') return 'docx'
|
||
if (ext === 'xlsx') return 'xlsx'
|
||
if (ext === 'xls') return 'xls'
|
||
return 'other'
|
||
}
|
||
},
|
||
created() {
|
||
this.getList()
|
||
},
|
||
watch: {
|
||
selectedFile(val) {
|
||
if (val) {
|
||
this.loadComments()
|
||
} else {
|
||
this.comments = []
|
||
this.commentExpanded = false
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
/** 切换tab */
|
||
handleTabClick(tab) {
|
||
this.resetQuery()
|
||
},
|
||
/** 查询文件列表 */
|
||
getList() {
|
||
this.loading = true
|
||
const params = this.addDateRange(this.queryParams, this.dateRange)
|
||
// 根据tab设置额外参数
|
||
if (this.activeTab === 'my') {
|
||
params.createBy = this.$store.getters.name
|
||
} else if (this.activeTab === 'share') {
|
||
params.scopeType = 1
|
||
}
|
||
|
||
// 根据tab选择不同的API
|
||
let request
|
||
if (this.activeTab === 'related') {
|
||
request = listRelatedToMe(params)
|
||
} else {
|
||
request = listFile(params)
|
||
}
|
||
|
||
request.then(response => {
|
||
this.fileList = response.rows
|
||
this.total = response.total
|
||
this.loading = false
|
||
// 加载统计数据
|
||
if (this.activeTab === 'all') {
|
||
this.loadStats()
|
||
}
|
||
}).catch(() => {
|
||
this.loading = false
|
||
})
|
||
},
|
||
/** 加载统计数据 */
|
||
loadStats() {
|
||
// 获取所有公开文件数量
|
||
listFile({ pageNum: 1, pageSize: 1, scopeType: 1 }).then(r => { this.stats.publicFiles = r.total })
|
||
// 获取所有私有文件数量
|
||
listFile({ pageNum: 1, pageSize: 1, scopeType: 2 }).then(r => { this.stats.privateFiles = r.total })
|
||
// 我上传的
|
||
listFile({ pageNum: 1, pageSize: 1, createBy: this.$store.getters.name }).then(r => { this.stats.myFiles = r.total })
|
||
// 总计
|
||
listFile({ pageNum: 1, pageSize: 1 }).then(r => { this.stats.totalFiles = r.total })
|
||
},
|
||
/** 格式化文件大小 */
|
||
formatFileSize(size) {
|
||
if (!size) return '0 B'
|
||
if (size < 1024) return size + ' B'
|
||
if (size < 1024 * 1024) return (size / 1024).toFixed(2) + ' KB'
|
||
return (size / 1024 / 1024).toFixed(2) + ' MB'
|
||
},
|
||
/** 是否可编辑 */
|
||
canEdit(row) {
|
||
if (this.activeTab === 'related') return false
|
||
if (this.activeTab === 'share') return false
|
||
// 我的文件tab 或 所有文件tab中属于当前用户的
|
||
return row.createBy === this.$store.getters.name
|
||
},
|
||
/** 取消按钮 */
|
||
cancel() {
|
||
this.open = false
|
||
this.reset()
|
||
},
|
||
/** 对话框关闭回调 */
|
||
handleDialogClose() {
|
||
this.reset()
|
||
},
|
||
/** 表单重置 */
|
||
reset() {
|
||
this.form = {
|
||
fileId: undefined,
|
||
fileName: undefined,
|
||
filePath: undefined,
|
||
fileSize: undefined,
|
||
suffix: undefined,
|
||
orderNo: undefined,
|
||
dept: undefined,
|
||
fileType: undefined,
|
||
scopeType: 1,
|
||
visibleUsers: [],
|
||
remark: undefined
|
||
}
|
||
this.uploadFileList = []
|
||
this.originalVisibleUsers = []
|
||
this.isEdit = false
|
||
this.resetForm('form')
|
||
},
|
||
/** 可见范围切换 */
|
||
handleScopeChange(val) {
|
||
if (val === 1) {
|
||
this.form.visibleUsers = []
|
||
}
|
||
},
|
||
/** 搜索按钮 */
|
||
handleQuery() {
|
||
this.queryParams.pageNum = 1
|
||
this.getList()
|
||
},
|
||
/** 重置按钮 */
|
||
resetQuery() {
|
||
this.dateRange = []
|
||
this.selectedFile = null
|
||
this.resetForm('queryForm')
|
||
this.queryParams = {
|
||
pageNum: 1,
|
||
pageSize: 20,
|
||
fileName: undefined,
|
||
orderNo: undefined,
|
||
dept: undefined,
|
||
fileType: undefined,
|
||
scopeType: undefined
|
||
}
|
||
this.handleQuery()
|
||
},
|
||
/** 多选框 */
|
||
handleSelectionChange(selection) {
|
||
this.ids = selection.map(item => item.fileId)
|
||
this.single = selection.length !== 1
|
||
this.multiple = !selection.length
|
||
},
|
||
/** 新增/上传 */
|
||
handleAdd() {
|
||
this.reset()
|
||
this.isEdit = false
|
||
this.open = true
|
||
this.dialogTitle = '上传文件'
|
||
},
|
||
/** 修改 */
|
||
handleUpdate(row) {
|
||
this.reset()
|
||
this.isEdit = true
|
||
const fileId = row.fileId || this.ids[0]
|
||
getFile(fileId).then(response => {
|
||
const data = response.data
|
||
this.form = {
|
||
fileId: data.fileId,
|
||
fileName: data.fileName,
|
||
filePath: data.filePath,
|
||
fileSize: data.fileSize,
|
||
suffix: data.suffix,
|
||
orderNo: data.orderNo,
|
||
dept: data.dept,
|
||
fileType: data.fileType,
|
||
scopeType: data.scopeType,
|
||
visibleUsers: [],
|
||
remark: data.remark
|
||
}
|
||
// 如果是私有文件,加载可见用户
|
||
if (data.scopeType === 2) {
|
||
listVisibleUserByFileId(fileId).then(res => {
|
||
const rows = res.rows || []
|
||
this.form.visibleUsers = rows.map(r => r.userId)
|
||
this.originalVisibleUsers = [...this.form.visibleUsers]
|
||
})
|
||
}
|
||
this.open = true
|
||
this.dialogTitle = '编辑文件'
|
||
})
|
||
},
|
||
/** 上传前校验 */
|
||
handleBeforeUpload(file) {
|
||
// 自动设置文件名
|
||
if (!this.form.fileName) {
|
||
this.form.fileName = file.name
|
||
}
|
||
const ext = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase()
|
||
this.form.suffix = ext
|
||
this.form.fileSize = file.size
|
||
},
|
||
/** 上传成功 */
|
||
handleUploadSuccess(response, file, fileList) {
|
||
if (response.code === 200) {
|
||
this.form.filePath = response.data.url
|
||
this.form.fileName = this.form.fileName || response.data.fileName
|
||
this.$modal.msgSuccess('文件上传成功')
|
||
// 自动提交表单
|
||
this.doSubmit()
|
||
} else {
|
||
this.$modal.msgError(response.msg || '上传失败')
|
||
}
|
||
},
|
||
/** 上传失败 */
|
||
handleUploadError(err, file, fileList) {
|
||
this.$modal.msgError('文件上传失败')
|
||
},
|
||
/** 提交表单 */
|
||
submitForm() {
|
||
this.$refs['form'].validate(valid => {
|
||
if (valid) {
|
||
if (this.isEdit) {
|
||
// 编辑模式直接提交
|
||
this.doSubmit()
|
||
} else {
|
||
// 新增模式,先上传文件
|
||
if (this.$refs.upload && this.$refs.upload.uploadFiles.length > 0) {
|
||
this.$refs.upload.submit()
|
||
} else {
|
||
this.$modal.msgWarning('请选择要上传的文件')
|
||
}
|
||
}
|
||
}
|
||
})
|
||
},
|
||
/** 执行提交 */
|
||
doSubmit() {
|
||
const data = { ...this.form }
|
||
const visibleUsers = data.visibleUsers
|
||
delete data.visibleUsers
|
||
|
||
if (data.fileId) {
|
||
// 编辑模式
|
||
updateFile(data).then(response => {
|
||
this.saveVisibleUsers(data.fileId, visibleUsers)
|
||
this.$modal.msgSuccess('修改成功')
|
||
this.open = false
|
||
this.getList()
|
||
}).catch(() => {})
|
||
} else {
|
||
// 新增模式
|
||
addFile(data).then(response => {
|
||
const newFileId = response.data
|
||
if (newFileId && visibleUsers && visibleUsers.length > 0) {
|
||
this.saveVisibleUsers(newFileId, visibleUsers)
|
||
}
|
||
this.$modal.msgSuccess('新增成功')
|
||
this.open = false
|
||
this.getList()
|
||
}).catch(() => {})
|
||
}
|
||
},
|
||
/** 保存可见用户 */
|
||
saveVisibleUsers(fileId, visibleUsers) {
|
||
if (!visibleUsers || visibleUsers.length === 0) {
|
||
// 删除所有可见用户
|
||
if (this.originalVisibleUsers.length > 0) {
|
||
delVisibleUser(this.originalVisibleUsers.join(','))
|
||
}
|
||
return
|
||
}
|
||
// 新增的
|
||
const toAdd = visibleUsers.filter(u => !this.originalVisibleUsers.includes(u))
|
||
// 删除的
|
||
const toDel = this.originalVisibleUsers.filter(u => !visibleUsers.includes(u))
|
||
|
||
// 删除
|
||
if (toDel.length > 0) {
|
||
// 需要先查出visibleId
|
||
listVisibleUserByFileId(fileId).then(res => {
|
||
const rows = res.rows || []
|
||
const delIds = rows.filter(r => toDel.includes(r.userId)).map(r => r.visibleId)
|
||
if (delIds.length > 0) {
|
||
delVisibleUser(delIds.join(','))
|
||
}
|
||
})
|
||
}
|
||
// 新增
|
||
toAdd.forEach(userId => {
|
||
addVisibleUser({ fileId, userId })
|
||
})
|
||
},
|
||
/** 删除 */
|
||
handleDelete(row) {
|
||
const fileIds = row.fileId || this.ids.join(',')
|
||
this.$modal.confirm('是否确认删除所选文件?').then(() => {
|
||
return delFile(fileIds)
|
||
}).then(() => {
|
||
this.getList()
|
||
this.$modal.msgSuccess('删除成功')
|
||
}).catch(() => {})
|
||
},
|
||
/** 导出 */
|
||
handleExport() {
|
||
this.download('system/file/export', {
|
||
...this.queryParams
|
||
}, `file_${new Date().getTime()}.xlsx`)
|
||
},
|
||
/** 预览 */
|
||
handlePreview(row) {
|
||
if (this.activeTab === 'share' || this.activeTab === 'related') {
|
||
this.selectedFile = row
|
||
incrementView(row.fileId)
|
||
} else {
|
||
this.previewFile = row
|
||
this.previewTitle = '文件详情 - ' + row.fileName
|
||
this.previewVisible = true
|
||
}
|
||
},
|
||
/** 点击行选中预览(共享文件 / 与我相关 tab) */
|
||
handleRowClick(row) {
|
||
if (this.activeTab === 'share' || this.activeTab === 'related') {
|
||
this.selectedFile = row
|
||
}
|
||
},
|
||
/** 查看文件详情 */
|
||
handleShowInfo(row) {
|
||
this.previewFile = row
|
||
this.previewTitle = '文件详情 - ' + row.fileName
|
||
this.previewVisible = true
|
||
},
|
||
/** 下载文件 */
|
||
downloadFile(row) {
|
||
if (row.filePath) {
|
||
window.open(row.filePath, '_blank')
|
||
}
|
||
},
|
||
/** 加载评论列表 */
|
||
loadComments() {
|
||
this.commentLoading = true
|
||
listFileComment(this.selectedFile.fileId).then(res => {
|
||
this.comments = res.data || []
|
||
this.commentLoading = false
|
||
}).catch(() => {
|
||
this.commentLoading = false
|
||
})
|
||
},
|
||
/** 发送评论 */
|
||
handleAddComment() {
|
||
const content = this.commentInput.trim()
|
||
if (!content) return
|
||
addFileComment({ fileId: this.selectedFile.fileId, content }).then(() => {
|
||
this.commentInput = ''
|
||
this.loadComments()
|
||
})
|
||
},
|
||
/** 开始拖拽 */
|
||
startResize(e) {
|
||
this.isDragging = true
|
||
this.startX = e.clientX
|
||
this.startLeftWidth = parseFloat(this.leftPanelWidth)
|
||
document.addEventListener('mousemove', this.doResize)
|
||
document.addEventListener('mouseup', this.stopResize)
|
||
document.body.style.cursor = 'col-resize'
|
||
document.body.style.userSelect = 'none'
|
||
},
|
||
/** 拖拽中 */
|
||
doResize(e) {
|
||
if (!this.isDragging) return
|
||
const container = this.$refs.layoutContainer
|
||
const rect = container.getBoundingClientRect()
|
||
const deltaX = e.clientX - this.startX
|
||
let percent = this.startLeftWidth + (deltaX / rect.width) * 100
|
||
percent = Math.max(30, Math.min(80, percent))
|
||
this.leftPanelWidth = percent + '%'
|
||
},
|
||
/** 结束拖拽 */
|
||
stopResize() {
|
||
if (!this.isDragging) return
|
||
this.isDragging = false
|
||
document.removeEventListener('mousemove', this.doResize)
|
||
document.removeEventListener('mouseup', this.stopResize)
|
||
document.body.style.cursor = ''
|
||
document.body.style.userSelect = ''
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.stat-row {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
text-align: center;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
border-left: 4px solid #409eff;
|
||
}
|
||
|
||
.stat-card.public {
|
||
border-left-color: #67c23a;
|
||
}
|
||
|
||
.stat-card.private {
|
||
border-left-color: #e6a23c;
|
||
}
|
||
|
||
.stat-num {
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
color: #303133;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.file-preview-container {
|
||
padding: 8px;
|
||
}
|
||
|
||
.upload-demo {
|
||
width: 100%;
|
||
}
|
||
|
||
/* 左右分栏布局 */
|
||
.file-layout {
|
||
display: flex;
|
||
align-items: stretch;
|
||
gap: 0;
|
||
}
|
||
|
||
.file-left {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: calc(100vh - 240px);
|
||
}
|
||
|
||
.file-right {
|
||
flex: 1;
|
||
min-width: 300px;
|
||
}
|
||
|
||
/* 拖拽分隔条 */
|
||
.resize-handle {
|
||
width: 6px;
|
||
cursor: col-resize;
|
||
flex-shrink: 0;
|
||
align-self: stretch;
|
||
background: transparent;
|
||
position: relative;
|
||
transition: background-color 0.2s;
|
||
margin: 0 2px;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.resize-handle:hover,
|
||
.resize-handle.dragging {
|
||
background: #409eff;
|
||
}
|
||
|
||
.resize-handle::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 2px;
|
||
height: 30px;
|
||
background: #dcdfe6;
|
||
border-radius: 1px;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.resize-handle:hover::after,
|
||
.resize-handle.dragging::after {
|
||
background: #fff;
|
||
}
|
||
|
||
.preview-panel {
|
||
background: #fff;
|
||
border: 1px solid #ebeef5;
|
||
border-radius: 4px;
|
||
height: 100%;
|
||
min-height: calc(100vh - 300px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.preview-empty {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.preview-header {
|
||
padding: 12px 16px;
|
||
border-bottom: 1px solid #ebeef5;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.preview-filename {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #303133;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.preview-comment {
|
||
border-bottom: 1px solid #ebeef5;
|
||
}
|
||
|
||
.comment-bar {
|
||
padding: 8px 16px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
color: #606266;
|
||
user-select: none;
|
||
}
|
||
|
||
.comment-bar:hover {
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
.comment-toggle {
|
||
color: #909399;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.comment-body {
|
||
max-height: 260px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.comment-hint {
|
||
padding: 16px;
|
||
text-align: center;
|
||
color: #909399;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.comment-list {
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.comment-item {
|
||
padding: 8px 16px;
|
||
border-bottom: 1px solid #f2f2f2;
|
||
}
|
||
|
||
.comment-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.comment-meta {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
font-size: 12px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.comment-dept {
|
||
color: #409eff;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.comment-user {
|
||
color: #606266;
|
||
}
|
||
|
||
.comment-time {
|
||
color: #c0c4cc;
|
||
}
|
||
|
||
.comment-text {
|
||
font-size: 13px;
|
||
color: #303133;
|
||
line-height: 1.5;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.comment-input-area {
|
||
padding: 8px 16px;
|
||
border-top: 1px solid #f2f2f2;
|
||
}
|
||
|
||
.preview-content {
|
||
overflow: auto;
|
||
display: flex;
|
||
}
|
||
|
||
.preview-content > div {
|
||
width: 100%;
|
||
}
|
||
|
||
.preview-not-supported {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 400px;
|
||
}
|
||
|
||
/* 左侧表格区域撑满剩余空间可滚动(仅 share / related tab) */
|
||
.file-table-wrapper.scrolling {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* 左侧文件元数据卡片 */
|
||
.file-meta-card {
|
||
margin-top: 12px;
|
||
}
|
||
</style>
|
||
|
||
<!-- 表格选中行高亮(非 scoped,穿透 el-table) -->
|
||
<style>
|
||
.file-left .el-table .current-row > td {
|
||
background-color: #ecf5ff !important;
|
||
}
|
||
</style>
|