feat(file): 添加文件管理功能和相关接口

- 新增文件上传、编辑、删除等基础功能
- 实现文件可见范围控制(公开/私有)
- 添加与我相关的文件查询功能
- 集成用户选择组件用于设置文件可见用户
- 实现文件统计展示功能
- 完善文件操作权限验证机制
- 添加文件预览和下载功能
- 优化文件列表分页和搜索功能
This commit is contained in:
2026-06-30 10:24:40 +08:00
parent d4b5bc7041
commit b93f953327
5 changed files with 878 additions and 2 deletions

View File

@@ -71,8 +71,9 @@ public class SysFileController extends BaseController {
@Log(title = "文件主信息", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysFileBo bo) {
return toAjax(iSysFileService.insertByBo(bo));
public R<Long> add(@Validated(AddGroup.class) @RequestBody SysFileBo bo) {
iSysFileService.insertByBo(bo);
return R.ok(bo.getFileId());
}
/**
@@ -96,4 +97,12 @@ public class SysFileController extends BaseController {
@PathVariable Long[] fileIds) {
return toAjax(iSysFileService.deleteWithValidByIds(Arrays.asList(fileIds), true));
}
/**
* 查询与我相关的文件(私有文件且当前用户在可见用户列表中)
*/
@GetMapping("/relatedToMe")
public TableDataInfo<SysFileVo> relatedToMe(SysFileBo bo, PageQuery pageQuery) {
return iSysFileService.queryPageListRelatedToMe(bo, pageQuery);
}
}

View File

@@ -46,4 +46,9 @@ public interface ISysFileService {
* 校验并批量删除文件主信息信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 查询与我相关的文件(私有文件且当前用户在可见用户列表中)
*/
TableDataInfo<SysFileVo> queryPageListRelatedToMe(SysFileBo bo, PageQuery pageQuery);
}

View File

@@ -6,18 +6,22 @@ import com.klp.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.klp.common.helper.LoginHelper;
import com.klp.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.klp.system.domain.bo.SysFileBo;
import com.klp.system.domain.vo.SysFileVo;
import com.klp.system.domain.SysFile;
import com.klp.system.domain.SysFileVisibleUser;
import com.klp.system.mapper.SysFileMapper;
import com.klp.system.mapper.SysFileVisibleUserMapper;
import com.klp.system.service.ISysFileService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.stream.Collectors;
/**
* 文件主信息Service业务层处理
@@ -30,6 +34,7 @@ import java.util.Collection;
public class SysFileServiceImpl implements ISysFileService {
private final SysFileMapper baseMapper;
private final SysFileVisibleUserMapper visibleUserMapper;
/**
* 查询文件主信息
@@ -69,6 +74,7 @@ public class SysFileServiceImpl implements ISysFileService {
lqw.eq(StringUtils.isNotBlank(bo.getDept()), SysFile::getDept, bo.getDept());
lqw.eq(StringUtils.isNotBlank(bo.getFileType()), SysFile::getFileType, bo.getFileType());
lqw.eq(bo.getScopeType() != null, SysFile::getScopeType, bo.getScopeType());
lqw.eq(StringUtils.isNotBlank(bo.getCreateBy()), SysFile::getCreateBy, bo.getCreateBy());
return lqw;
}
@@ -96,6 +102,28 @@ public class SysFileServiceImpl implements ISysFileService {
return baseMapper.updateById(update) > 0;
}
/**
* 查询与我相关的文件(私有文件且当前用户在可见用户列表中)
*/
@Override
public TableDataInfo<SysFileVo> queryPageListRelatedToMe(SysFileBo bo, PageQuery pageQuery) {
Long currentUserId = LoginHelper.getUserId();
// 查询当前用户可见的私有文件ID列表
LambdaQueryWrapper<SysFileVisibleUser> vuLqw = Wrappers.lambdaQuery();
vuLqw.eq(SysFileVisibleUser::getUserId, currentUserId);
List<SysFileVisibleUser> visibleList = visibleUserMapper.selectList(vuLqw);
List<Long> fileIds = visibleList.stream().map(SysFileVisibleUser::getFileId).distinct().collect(Collectors.toList());
if (fileIds.isEmpty()) {
return TableDataInfo.build(new Page<>());
}
LambdaQueryWrapper<SysFile> lqw = buildQueryWrapper(bo);
lqw.in(SysFile::getFileId, fileIds);
Page<SysFileVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 保存前的数据校验
*/

View File

@@ -0,0 +1,99 @@
import request from '@/utils/request'
// 查询文件列表
export function listFile(query) {
return request({
url: '/system/file/list',
method: 'get',
params: query
})
}
// 查询文件详细
export function getFile(fileId) {
return request({
url: '/system/file/' + fileId,
method: 'get'
})
}
// 新增文件
export function addFile(data) {
return request({
url: '/system/file',
method: 'post',
data: data
})
}
// 修改文件
export function updateFile(data) {
return request({
url: '/system/file',
method: 'put',
data: data
})
}
// 删除文件
export function delFile(fileIds) {
return request({
url: '/system/file/' + fileIds,
method: 'delete'
})
}
// 导出文件列表
export function exportFile(query) {
return request({
url: '/system/file/export',
method: 'post',
params: query
})
}
// 查询与我相关的文件
export function listRelatedToMe(query) {
return request({
url: '/system/file/relatedToMe',
method: 'get',
params: query
})
}
// ============ 文件可见用户关联 ============
// 查询可见用户列表
export function listVisibleUser(query) {
return request({
url: '/system/fileVisibleUser/list',
method: 'get',
params: query
})
}
// 新增可见用户关联
export function addVisibleUser(data) {
return request({
url: '/system/fileVisibleUser',
method: 'post',
data: data
})
}
// 删除可见用户关联
export function delVisibleUser(visibleIds) {
return request({
url: '/system/fileVisibleUser/' + visibleIds,
method: 'delete'
})
}
// 按文件ID查询可见用户列表
export function listVisibleUserByFileId(fileId) {
return request({
url: '/system/fileVisibleUser/list',
method: 'get',
params: { fileId: fileId }
})
}

View File

@@ -0,0 +1,735 @@
<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="all"></el-tab-pane>
<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>
<!-- 搜索栏 -->
<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>
<!-- 文件列表表格 -->
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange">
<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">
<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">
<template slot-scope="scope">
{{ formatFileSize(scope.row.fileSize) }}
</template>
</el-table-column>
<el-table-column label="订单编号" align="center" prop="orderNo" :show-overflow-tooltip="true" />
<el-table-column label="所属部门" align="center" prop="dept" :show-overflow-tooltip="true" />
<el-table-column label="可见范围" align="center" prop="scopeType">
<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" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width">
<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"
/>
<!-- 上传/编辑对话框 -->
<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>
<!-- 文件预览对话框 -->
<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 } from '@/api/system/file'
import { getToken } from '@/utils/auth'
import UserSelect from '@/components/KLPService/UserSelect/index'
export default {
name: 'SysFile',
dicts: ['sys_file_type'],
components: {
UserSelect
},
data() {
return {
// 当前激活tab
activeTab: 'all',
// 遮罩层
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: [],
// 预览
previewVisible: false,
previewTitle: '',
previewFile: null
}
},
created() {
this.getList()
},
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.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) {
this.previewFile = row
this.previewTitle = '文件详情 - ' + row.fileName
this.previewVisible = true
},
/** 下载文件 */
downloadFile(row) {
if (row.filePath) {
window.open(row.filePath, '_blank')
}
}
}
}
</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%;
}
</style>