feat(file): 重构文件管理页面增加左右分栏布局
- 实现共享文件标签页的左右分栏布局,左侧显示文件列表,右侧显示文件预览 - 添加拖拽分隔条功能,支持调整左右面板宽度比例 - 为共享文件添加文件预览功能,支持图片、PDF、Word、Excel等多种格式 - 实现文件评论系统,支持查看和发布评论 - 优化表格交互,在共享文件标签页点击行可选中文件并显示预览 - 添加浏览次数统计功能,点击预览时自动增加浏览量 - 调整文件列表列显示逻辑,根据不同标签页显示相应字段 - 添加文件详情预览面板,显示文件元数据信息 - 实现评论区域展开收起功能,优化界面空间利用 -
This commit is contained in:
@@ -35,179 +35,258 @@
|
|||||||
<el-tab-pane label="与我相关" name="related"></el-tab-pane>
|
<el-tab-pane label="与我相关" name="related"></el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
<!-- 搜索栏 -->
|
<!-- 左右分栏布局 -->
|
||||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
<div class="file-layout" ref="layoutContainer">
|
||||||
<el-form-item label="文件名称" prop="fileName">
|
<!-- 左侧:文件列表 -->
|
||||||
<el-input
|
<div class="file-left" :style="{ width: activeTab === 'share' ? leftPanelWidth : '100%' }">
|
||||||
v-model="queryParams.fileName"
|
<!-- 搜索栏 -->
|
||||||
placeholder="请输入文件名称"
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
|
||||||
clearable
|
<el-form-item label="文件名称" prop="fileName">
|
||||||
style="width: 200px"
|
<el-input
|
||||||
@keyup.enter.native="handleQuery"
|
v-model="queryParams.fileName"
|
||||||
/>
|
placeholder="请输入文件名称"
|
||||||
</el-form-item>
|
clearable
|
||||||
<el-form-item label="订单编号" prop="orderNo">
|
style="width: 200px"
|
||||||
<el-input
|
@keyup.enter.native="handleQuery"
|
||||||
v-model="queryParams.orderNo"
|
/>
|
||||||
placeholder="请输入订单编号"
|
</el-form-item>
|
||||||
clearable
|
<el-form-item label="订单编号" prop="orderNo">
|
||||||
style="width: 200px"
|
<el-input
|
||||||
@keyup.enter.native="handleQuery"
|
v-model="queryParams.orderNo"
|
||||||
/>
|
placeholder="请输入订单编号"
|
||||||
</el-form-item>
|
clearable
|
||||||
<el-form-item label="所属部门" prop="dept">
|
style="width: 200px"
|
||||||
<el-input
|
@keyup.enter.native="handleQuery"
|
||||||
v-model="queryParams.dept"
|
/>
|
||||||
placeholder="请输入部门"
|
</el-form-item>
|
||||||
clearable
|
<el-form-item label="所属部门" prop="dept">
|
||||||
style="width: 200px"
|
<el-input
|
||||||
@keyup.enter.native="handleQuery"
|
v-model="queryParams.dept"
|
||||||
/>
|
placeholder="请输入部门"
|
||||||
</el-form-item>
|
clearable
|
||||||
<el-form-item label="文件类型" prop="fileType">
|
style="width: 200px"
|
||||||
<el-select
|
@keyup.enter.native="handleQuery"
|
||||||
v-model="queryParams.fileType"
|
/>
|
||||||
placeholder="请选择文件类型"
|
</el-form-item>
|
||||||
clearable
|
<el-form-item label="文件类型" prop="fileType">
|
||||||
style="width: 200px"
|
<el-select
|
||||||
>
|
v-model="queryParams.fileType"
|
||||||
<el-option
|
placeholder="请选择文件类型"
|
||||||
v-for="item in dict.type.sys_file_type"
|
clearable
|
||||||
:key="item.value"
|
style="width: 200px"
|
||||||
:label="item.label"
|
>
|
||||||
:value="item.value"
|
<el-option
|
||||||
/>
|
v-for="item in dict.type.sys_file_type"
|
||||||
</el-select>
|
:key="item.value"
|
||||||
</el-form-item>
|
:label="item.label"
|
||||||
<el-form-item label="可见范围" prop="scopeType" v-if="activeTab !== 'share' && activeTab !== 'related'">
|
:value="item.value"
|
||||||
<el-select
|
/>
|
||||||
v-model="queryParams.scopeType"
|
</el-select>
|
||||||
placeholder="请选择可见范围"
|
</el-form-item>
|
||||||
clearable
|
<el-form-item label="可见范围" prop="scopeType" v-if="activeTab !== 'share' && activeTab !== 'related'">
|
||||||
style="width: 200px"
|
<el-select
|
||||||
>
|
v-model="queryParams.scopeType"
|
||||||
<el-option label="公开" :value="1" />
|
placeholder="请选择可见范围"
|
||||||
<el-option label="私有" :value="2" />
|
clearable
|
||||||
</el-select>
|
style="width: 200px"
|
||||||
</el-form-item>
|
>
|
||||||
<el-form-item label="上传时间">
|
<el-option label="公开" :value="1" />
|
||||||
<el-date-picker
|
<el-option label="私有" :value="2" />
|
||||||
v-model="dateRange"
|
</el-select>
|
||||||
style="width: 240px"
|
</el-form-item>
|
||||||
value-format="yyyy-MM-dd HH:mm:ss"
|
<el-form-item label="上传时间">
|
||||||
type="daterange"
|
<el-date-picker
|
||||||
range-separator="-"
|
v-model="dateRange"
|
||||||
start-placeholder="开始日期"
|
style="width: 240px"
|
||||||
end-placeholder="结束日期"
|
value-format="yyyy-MM-dd HH:mm:ss"
|
||||||
:default-time="['00:00:00', '23:59:59']"
|
type="daterange"
|
||||||
></el-date-picker>
|
range-separator="-"
|
||||||
</el-form-item>
|
start-placeholder="开始日期"
|
||||||
<el-form-item>
|
end-placeholder="结束日期"
|
||||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
:default-time="['00:00:00', '23:59:59']"
|
||||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
></el-date-picker>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
<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-row :gutter="10" class="mb8">
|
||||||
<el-col :span="1.5" v-if="activeTab === 'my' || activeTab === 'all'">
|
<el-col :span="1.5" v-if="activeTab === 'my' || activeTab === 'all'">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
icon="el-icon-plus"
|
icon="el-icon-plus"
|
||||||
size="mini"
|
size="mini"
|
||||||
@click="handleAdd"
|
@click="handleAdd"
|
||||||
>上传文件</el-button>
|
>上传文件</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5" v-if="activeTab === 'my' || activeTab === 'all'">
|
<el-col :span="1.5" v-if="activeTab === 'my' || activeTab === 'all'">
|
||||||
<el-button
|
<el-button
|
||||||
type="danger"
|
type="danger"
|
||||||
plain
|
plain
|
||||||
icon="el-icon-delete"
|
icon="el-icon-delete"
|
||||||
size="mini"
|
size="mini"
|
||||||
:disabled="multiple"
|
:disabled="multiple"
|
||||||
@click="handleDelete"
|
@click="handleDelete"
|
||||||
>删除</el-button>
|
>删除</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
plain
|
plain
|
||||||
icon="el-icon-download"
|
icon="el-icon-download"
|
||||||
size="mini"
|
size="mini"
|
||||||
@click="handleExport"
|
@click="handleExport"
|
||||||
>导出</el-button>
|
>导出</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<!-- 文件列表表格 -->
|
<!-- 文件列表表格 -->
|
||||||
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange">
|
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange" :highlight-current-row="activeTab === 'share'" @row-click="handleRowClick">
|
||||||
<el-table-column type="selection" width="55" align="center" v-if="activeTab === 'my' || activeTab === 'all'" />
|
<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">
|
<el-table-column label="文件名称" align="center" prop="fileName" :show-overflow-tooltip="true">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-link type="primary" @click="handlePreview(scope.row)">{{ scope.row.fileName }}</el-link>
|
<el-link type="primary" @click="handlePreview(scope.row)">{{ scope.row.fileName }}</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="文件类型" align="center" prop="fileType">
|
<el-table-column label="文件类型" align="center" prop="fileType" v-if="activeTab !== 'share'">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<dict-tag :options="dict.type.sys_file_type" :value="scope.row.fileType"/>
|
<dict-tag :options="dict.type.sys_file_type" :value="scope.row.fileType"/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="文件大小" align="center" prop="fileSize">
|
<el-table-column label="文件大小" align="center" prop="fileSize" v-if="activeTab !== 'share'">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
{{ formatFileSize(scope.row.fileSize) }}
|
{{ formatFileSize(scope.row.fileSize) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="订单编号" align="center" prop="orderNo" :show-overflow-tooltip="true" />
|
<el-table-column label="订单编号" align="center" prop="orderNo" :show-overflow-tooltip="true" v-if="activeTab !== 'share'" />
|
||||||
<el-table-column label="所属部门" align="center" prop="dept" :show-overflow-tooltip="true" />
|
<el-table-column label="所属部门" align="center" prop="dept" :show-overflow-tooltip="true" v-if="activeTab !== 'share'" />
|
||||||
<el-table-column label="可见范围" align="center" prop="scopeType">
|
<el-table-column label="可见范围" align="center" prop="scopeType" v-if="activeTab !== 'share'">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag :type="scope.row.scopeType === 1 ? 'success' : 'warning'" size="small">
|
<el-tag :type="scope.row.scopeType === 1 ? 'success' : 'warning'" size="small">
|
||||||
{{ scope.row.scopeType === 1 ? '公开' : '私有' }}
|
{{ scope.row.scopeType === 1 ? '公开' : '私有' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="上传人" align="center" prop="createBy" />
|
<el-table-column label="上传人" align="center" prop="createBy" />
|
||||||
<el-table-column label="上传时间" align="center" prop="createTime" width="160">
|
<el-table-column label="上传时间" align="center" prop="createTime" width="160">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
<el-table-column label="浏览次数" align="center" width="90" v-if="activeTab === 'share'">
|
||||||
<el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width">
|
<template slot-scope="scope">
|
||||||
<template slot-scope="scope">
|
<span>{{ scope.row.viewCount != null ? scope.row.viewCount : 0 }}</span>
|
||||||
<el-button
|
</template>
|
||||||
size="mini"
|
</el-table-column>
|
||||||
type="text"
|
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" v-if="activeTab !== 'share'" />
|
||||||
icon="el-icon-view"
|
<el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width" v-if="activeTab !== 'share'">
|
||||||
@click="handlePreview(scope.row)"
|
<template slot-scope="scope">
|
||||||
>预览</el-button>
|
<el-button
|
||||||
<el-button
|
size="mini"
|
||||||
v-if="canEdit(scope.row)"
|
type="text"
|
||||||
size="mini"
|
icon="el-icon-view"
|
||||||
type="text"
|
@click="handlePreview(scope.row)"
|
||||||
icon="el-icon-edit"
|
>预览</el-button>
|
||||||
@click="handleUpdate(scope.row)"
|
<el-button
|
||||||
>编辑</el-button>
|
v-if="canEdit(scope.row)"
|
||||||
<el-button
|
size="mini"
|
||||||
v-if="canEdit(scope.row)"
|
type="text"
|
||||||
size="mini"
|
icon="el-icon-edit"
|
||||||
type="text"
|
@click="handleUpdate(scope.row)"
|
||||||
icon="el-icon-delete"
|
>编辑</el-button>
|
||||||
@click="handleDelete(scope.row)"
|
<el-button
|
||||||
>删除</el-button>
|
v-if="canEdit(scope.row)"
|
||||||
</template>
|
size="mini"
|
||||||
</el-table-column>
|
type="text"
|
||||||
</KLPTable>
|
icon="el-icon-delete"
|
||||||
|
@click="handleDelete(scope.row)"
|
||||||
|
>删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</KLPTable>
|
||||||
|
|
||||||
<pagination
|
<pagination
|
||||||
v-show="total>0"
|
v-show="total>0"
|
||||||
:total="total"
|
:total="total"
|
||||||
:page.sync="queryParams.pageNum"
|
:page.sync="queryParams.pageNum"
|
||||||
:limit.sync="queryParams.pageSize"
|
:limit.sync="queryParams.pageSize"
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 拖拽分隔条(仅共享文件 tab) -->
|
||||||
|
<div class="resize-handle" v-if="activeTab === 'share'" @mousedown="startResize" :class="{ dragging: isDragging }"></div>
|
||||||
|
|
||||||
|
<!-- 右侧:文件预览(仅共享文件 tab) -->
|
||||||
|
<div class="file-right" v-if="activeTab === 'share'">
|
||||||
|
<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-meta">
|
||||||
|
<span>{{ formatFileSize(selectedFile.fileSize) }}</span>
|
||||||
|
<dict-tag :options="dict.type.sys_file_type" :value="selectedFile.fileType" />
|
||||||
|
<span>{{ selectedFile.createBy }}</span>
|
||||||
|
<span>{{ parseTime(selectedFile.createTime) }}</span>
|
||||||
|
</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-dialog :title="dialogTitle" :visible.sync="open" width="650px" append-to-body @close="handleDialogClose">
|
||||||
@@ -272,7 +351,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 文件预览对话框 -->
|
<!-- 文件预览对话框(仅非共享文件 tab 使用) -->
|
||||||
<el-dialog :title="previewTitle" :visible.sync="previewVisible" width="80%" append-to-body>
|
<el-dialog :title="previewTitle" :visible.sync="previewVisible" width="80%" append-to-body>
|
||||||
<div v-if="previewFile" class="file-preview-container">
|
<div v-if="previewFile" class="file-preview-container">
|
||||||
<el-descriptions :column="2" border>
|
<el-descriptions :column="2" border>
|
||||||
@@ -302,15 +381,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listFile, getFile, addFile, updateFile, delFile, exportFile, listVisibleUser, addVisibleUser, delVisibleUser, listVisibleUserByFileId, listRelatedToMe } from '@/api/system/file'
|
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 { getToken } from '@/utils/auth'
|
||||||
import UserSelect from '@/components/KLPService/UserSelect/index'
|
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 {
|
export default {
|
||||||
name: 'SysFile',
|
name: 'SysFile',
|
||||||
dicts: ['sys_file_type'],
|
dicts: ['sys_file_type'],
|
||||||
components: {
|
components: {
|
||||||
UserSelect
|
UserSelect,
|
||||||
|
ImagePreview,
|
||||||
|
PdfPreview,
|
||||||
|
DocxPreview,
|
||||||
|
XlsxPreview,
|
||||||
|
XlsPreview
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -386,15 +476,51 @@ export default {
|
|||||||
Authorization: 'Bearer ' + getToken()
|
Authorization: 'Bearer ' + getToken()
|
||||||
},
|
},
|
||||||
uploadFileList: [],
|
uploadFileList: [],
|
||||||
// 预览
|
// 预览对话框(非共享文件 tab)
|
||||||
previewVisible: false,
|
previewVisible: false,
|
||||||
previewTitle: '',
|
previewTitle: '',
|
||||||
previewFile: null
|
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() {
|
created() {
|
||||||
this.getList()
|
this.getList()
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
selectedFile(val) {
|
||||||
|
if (val) {
|
||||||
|
this.loadComments()
|
||||||
|
} else {
|
||||||
|
this.comments = []
|
||||||
|
this.commentExpanded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 切换tab */
|
/** 切换tab */
|
||||||
handleTabClick(tab) {
|
handleTabClick(tab) {
|
||||||
@@ -499,6 +625,7 @@ export default {
|
|||||||
/** 重置按钮 */
|
/** 重置按钮 */
|
||||||
resetQuery() {
|
resetQuery() {
|
||||||
this.dateRange = []
|
this.dateRange = []
|
||||||
|
this.selectedFile = null
|
||||||
this.resetForm('queryForm')
|
this.resetForm('queryForm')
|
||||||
this.queryParams = {
|
this.queryParams = {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
@@ -675,15 +802,74 @@ export default {
|
|||||||
},
|
},
|
||||||
/** 预览 */
|
/** 预览 */
|
||||||
handlePreview(row) {
|
handlePreview(row) {
|
||||||
this.previewFile = row
|
if (this.activeTab === 'share') {
|
||||||
this.previewTitle = '文件详情 - ' + row.fileName
|
this.selectedFile = row
|
||||||
this.previewVisible = true
|
incrementView(row.fileId)
|
||||||
|
} else {
|
||||||
|
this.previewFile = row
|
||||||
|
this.previewTitle = '文件详情 - ' + row.fileName
|
||||||
|
this.previewVisible = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 点击行选中预览(共享文件 tab) */
|
||||||
|
handleRowClick(row) {
|
||||||
|
if (this.activeTab === 'share') {
|
||||||
|
this.selectedFile = row
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/** 下载文件 */
|
/** 下载文件 */
|
||||||
downloadFile(row) {
|
downloadFile(row) {
|
||||||
if (row.filePath) {
|
if (row.filePath) {
|
||||||
window.open(row.filePath, '_blank')
|
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 = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -731,4 +917,210 @@ export default {
|
|||||||
.upload-demo {
|
.upload-demo {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 左右分栏布局 */
|
||||||
|
.file-layout {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-left {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: calc(100vh - 280px);
|
||||||
|
min-height: 450px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: sticky;
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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-meta {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-not-supported {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- 表格选中行高亮(非 scoped,穿透 el-table) -->
|
||||||
|
<style>
|
||||||
|
.file-left .el-table .current-row > td {
|
||||||
|
background-color: #ecf5ff !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user