492 lines
18 KiB
Vue
492 lines
18 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||
<el-form-item label="文档名称" prop="docName">
|
||
<el-input
|
||
v-model="queryParams.docName"
|
||
placeholder="请输入文档名称"
|
||
clearable
|
||
@keyup.enter.native="handleQuery"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="上传者" prop="createBy">
|
||
<el-input
|
||
v-model="queryParams.createBy"
|
||
placeholder="请输入上传者"
|
||
clearable
|
||
@keyup.enter.native="handleQuery"
|
||
/>
|
||
</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">
|
||
<el-button
|
||
type="primary"
|
||
plain
|
||
icon="el-icon-upload"
|
||
size="mini"
|
||
@click="handleUpload"
|
||
v-hasPermi="['project:doc:upload']"
|
||
>上传文档</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button
|
||
type="danger"
|
||
plain
|
||
icon="el-icon-delete"
|
||
size="mini"
|
||
:disabled="multiple"
|
||
@click="handleDelete"
|
||
v-hasPermi="['project:doc:remove']"
|
||
>删除</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-table v-loading="loading" :data="docList" @selection-change="handleSelectionChange">
|
||
<el-table-column type="selection" width="55" align="center" />
|
||
<el-table-column label="文档名称" align="center" prop="docName" />
|
||
<el-table-column label="所属项目" align="center" prop="projectName" />
|
||
<el-table-column label="文档类型" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ (scope.row.docType) || (scope.row.docPath ? scope.row.docPath.split('.').pop() : '-') }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="文件大小" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ scope.row.docSize || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="上传者" align="center" prop="createBy" />
|
||
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
|
||
<template slot-scope="scope">
|
||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||
<template slot-scope="scope">
|
||
<el-button
|
||
size="mini"
|
||
type="text"
|
||
icon="el-icon-download"
|
||
@click="handleDownload(scope.row)"
|
||
v-hasPermi="['project:doc:download']"
|
||
>下载</el-button>
|
||
<el-button
|
||
size="mini"
|
||
type="text"
|
||
icon="el-icon-delete"
|
||
@click="handleDelete(scope.row)"
|
||
v-hasPermi="['project:doc:remove']"
|
||
>删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<pagination
|
||
v-show="total>0"
|
||
:total="total"
|
||
:page.sync="queryParams.pageNum"
|
||
:limit.sync="queryParams.pageSize"
|
||
@pagination="getList"
|
||
/>
|
||
|
||
<!-- 上传文档对话框 -->
|
||
<el-dialog :title="uploadTitle" :visible.sync="uploadOpen" width="600px" append-to-body>
|
||
<el-form label-width="90px" size="small" style="margin-bottom: 10px;">
|
||
<el-form-item label="文档类型">
|
||
<el-radio-group v-model="upload.meta.kindType" @change="handleKindTypeChange">
|
||
<el-radio :label="0" :disabled="isNormalUser">项目文档</el-radio>
|
||
<el-radio :label="1">模块文档</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="所属项目">
|
||
<el-select v-model="upload.meta.projectId" placeholder="请选择项目" filterable @change="handleProjectChange">
|
||
<el-option v-for="p in projectOptions" :key="p.projectId" :label="p.projectName" :value="p.projectId"/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item v-if="upload.meta.kindType === 1" label="所属模块">
|
||
<el-select v-model="upload.meta.moduleId" placeholder="请选择模块" filterable :disabled="!moduleOptions.length">
|
||
<el-option v-for="m in moduleOptions" :key="m.moduleId" :label="m.moduleName" :value="m.moduleId"/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
<el-upload
|
||
ref="upload"
|
||
:limit="1"
|
||
accept=".pdf,.doc,.docx,.txt"
|
||
:action="upload.url"
|
||
:headers="upload.headers"
|
||
:file-list="upload.fileList"
|
||
:data="upload.data"
|
||
:on-progress="handleFileUploadProgress"
|
||
:on-success="handleFileSuccess"
|
||
:auto-upload="false"
|
||
drag
|
||
>
|
||
<i class="el-icon-upload"></i>
|
||
<div class="el-upload__text">
|
||
将文件拖到此处,或<em>点击上传</em>
|
||
</div>
|
||
<div class="el-upload__tip" slot="tip">
|
||
<div>
|
||
<strong>文档类型:</strong>{{ upload.meta.kindType === 1 ? '模块文档' : '项目文档' }}<br>
|
||
<strong>所属项目:</strong>{{ (projectOptions.find(p=>String(p.projectId)===String(upload.meta.projectId))||{}).projectName || '-' }}<br>
|
||
<template v-if="upload.meta.kindType === 1">
|
||
<strong>所属模块:</strong>{{ (moduleOptions.find(m=>String(m.moduleId)===String(upload.meta.moduleId))||{}).moduleName || '-' }}
|
||
</template>
|
||
</div>
|
||
<br>
|
||
支持格式:PDF、DOC、DOCX、TXT
|
||
</div>
|
||
</el-upload>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||
<el-button @click="uploadOpen = false">取 消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { getToken } from '@/utils/auth'
|
||
import { listDoc, delDoc } from '@/api/project/doc'
|
||
import { listProject } from '@/api/project/project'
|
||
import { listModulesByProject, getMyModules } from '@/api/project/module'
|
||
|
||
export default {
|
||
name: "Doc",
|
||
computed: {
|
||
// 是否项目管理员/平台管理员
|
||
isProjectAdmin() {
|
||
const roles = this.$store.state.user.roles || []
|
||
return roles.some(r => {
|
||
const key = typeof r === 'string' ? r : r.roleKey
|
||
return key === 'project_admin' || key === 'platform_admin'
|
||
})
|
||
},
|
||
// 是否普通用户(且不是管理员)
|
||
isNormalUser() {
|
||
const roles = this.$store.state.user.roles || []
|
||
const hasNormal = roles.some(r => (typeof r === 'string' ? r : r.roleKey) === 'normal_user')
|
||
return hasNormal && !this.isProjectAdmin
|
||
},
|
||
// 当前用户ID(用于前端过滤:项目管理员仅看见自己创建的项目)
|
||
currentUserId() {
|
||
const u = this.$store.state.user || {}
|
||
return u.userId || u.user_id || u.id || null
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
// 遮罩层
|
||
loading: true,
|
||
// 选中数组
|
||
ids: [],
|
||
// 非多个禁用
|
||
multiple: true,
|
||
// 显示搜索条件
|
||
showSearch: true,
|
||
// 总条数
|
||
total: 0,
|
||
// 文档表格数据
|
||
docList: [],
|
||
// 弹出层标题
|
||
uploadTitle: "",
|
||
// 是否显示弹出层
|
||
uploadOpen: false,
|
||
// 查询参数
|
||
queryParams: {
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
docName: null,
|
||
createBy: null,
|
||
// 查询条件:根据上下文映射到 projectId 或 moduleId
|
||
projectId: null,
|
||
moduleId: null,
|
||
kind_type: null, // 文档类型:0=项目文档,1=模块文档
|
||
},
|
||
// 当前上下文信息
|
||
currentContext: {
|
||
pmodel_id: null,
|
||
kind_type: null,
|
||
moduleName: null,
|
||
projectId: null,
|
||
projectName: null,
|
||
isViewMode: false
|
||
},
|
||
// 上传参数
|
||
upload: {
|
||
// 是否禁用上传
|
||
isUploading: false,
|
||
// 设置上传的请求头部
|
||
headers: { Authorization: "Bearer " + getToken() },
|
||
// 上传的地址
|
||
url: process.env.VUE_APP_BASE_API + "/project/doc/upload",
|
||
// 上传的文件列表
|
||
fileList: [],
|
||
// 上传时的额外参数
|
||
data: {},
|
||
// 选择元数据(不直接污染data,最终同步到data里提交)
|
||
meta: {
|
||
kindType: 0,
|
||
projectId: null,
|
||
moduleId: null,
|
||
}
|
||
},
|
||
// 下拉选项
|
||
projectOptions: [],
|
||
moduleOptions: []
|
||
};
|
||
},
|
||
created() {
|
||
// 处理路由参数
|
||
this.handleRouteParams();
|
||
this.getList();
|
||
// 初始化项目下拉(仅加载当前用户相关项目:后端会做数据权限控制)
|
||
this.initProjects();
|
||
},
|
||
methods: {
|
||
async initProjects() {
|
||
try {
|
||
if (this.isProjectAdmin) {
|
||
// 项目管理员:仅显示自己创建的项目
|
||
const projRes = await listProject({ pageNum: 1, pageSize: 9999 })
|
||
const owned = (projRes && Array.isArray(projRes.rows))
|
||
? projRes.rows.filter(p => String(p.ownerId) === String(this.currentUserId))
|
||
: []
|
||
this.projectOptions = owned.map(p => ({ projectId: p.projectId, projectName: p.projectName || (`项目#${p.projectId}`) }))
|
||
} else {
|
||
// 普通用户:根据“我的模块”推导可见项目
|
||
const myModRes = await getMyModules({ pageNum: 1, pageSize: 9999 })
|
||
const map = new Map()
|
||
if (myModRes && Array.isArray(myModRes.rows)) {
|
||
myModRes.rows.forEach(m => {
|
||
if (m.projectId && !map.has(m.projectId)) {
|
||
map.set(m.projectId, { projectId: m.projectId, projectName: m.projectName || (`项目#${m.projectId}`) })
|
||
}
|
||
})
|
||
}
|
||
// 注:普通用户可保留路由上下文补入,管理员不补入非本人项目
|
||
if (this.currentContext.projectId && !map.has(this.currentContext.projectId)) {
|
||
map.set(this.currentContext.projectId, { projectId: this.currentContext.projectId, projectName: this.currentContext.projectName || (`项目#${this.currentContext.projectId}`) })
|
||
}
|
||
this.projectOptions = Array.from(map.values())
|
||
}
|
||
|
||
// 如果之前没选过,按上下文预选(仅当选项中存在)
|
||
if (!this.upload.meta.projectId && this.currentContext.projectId) {
|
||
const exists = this.projectOptions.some(p => String(p.projectId) === String(this.currentContext.projectId))
|
||
this.upload.meta.projectId = exists ? this.currentContext.projectId : null
|
||
}
|
||
if (this.upload.meta.projectId) {
|
||
await this.handleProjectChange(this.upload.meta.projectId)
|
||
} else {
|
||
this.moduleOptions = []
|
||
this.upload.meta.moduleId = null
|
||
}
|
||
} catch (e) {
|
||
this.projectOptions = []
|
||
}
|
||
},
|
||
/** 处理路由参数 */
|
||
handleRouteParams() {
|
||
const query = this.$route.query;
|
||
console.log('路由参数:', query); // 调试信息
|
||
|
||
if (query.pmodel_id) {
|
||
this.currentContext.pmodel_id = query.pmodel_id;
|
||
this.queryParams.pmodel_id = query.pmodel_id;
|
||
this.upload.data.pmodel_id = query.pmodel_id;
|
||
}
|
||
if (query.kind_type !== undefined && query.kind_type !== null) {
|
||
// 确保kind_type是数字类型
|
||
this.currentContext.kind_type = parseInt(query.kind_type);
|
||
this.queryParams.kind_type = parseInt(query.kind_type);
|
||
this.upload.data.kind_type = parseInt(query.kind_type);
|
||
}
|
||
if (query.moduleName) {
|
||
this.currentContext.moduleName = query.moduleName;
|
||
}
|
||
if (query.projectId) {
|
||
// 统一为数字,避免 el-select 由于类型不一致显示原始ID
|
||
this.currentContext.projectId = parseInt(query.projectId);
|
||
}
|
||
if (query.projectName) {
|
||
this.currentContext.projectName = query.projectName;
|
||
}
|
||
if (query.view === 'true') {
|
||
this.currentContext.isViewMode = true;
|
||
}
|
||
|
||
console.log('处理后的上下文:', this.currentContext); // 调试信息
|
||
},
|
||
handleKindTypeChange() {
|
||
if (this.upload.meta.kindType === 0) {
|
||
this.upload.meta.moduleId = null;
|
||
}
|
||
},
|
||
async handleProjectChange(projectId) {
|
||
this.upload.meta.moduleId = null;
|
||
if (!projectId) { this.moduleOptions = []; return; }
|
||
try {
|
||
if (!this.isProjectAdmin && this.isNormalUser) {
|
||
// 从“我的模块”中过滤出所选项目的可见模块
|
||
const { rows } = await getMyModules({ pageNum: 1, pageSize: 9999 })
|
||
this.moduleOptions = (rows || []).filter(m => String(m.projectId) === String(projectId))
|
||
} else {
|
||
const { rows } = await listModulesByProject(projectId)
|
||
this.moduleOptions = rows || []
|
||
}
|
||
} catch (e) {
|
||
this.moduleOptions = []
|
||
}
|
||
},
|
||
/** 查询文档列表 */
|
||
async getList() {
|
||
this.loading = true;
|
||
// 构建查询参数(根据上下文映射pmodel_id -> projectId/moduleId)
|
||
const query = { ...this.queryParams };
|
||
if (!query.projectId && !query.moduleId && this.currentContext.pmodel_id) {
|
||
if (this.currentContext.kind_type === 0) {
|
||
query.projectId = this.currentContext.pmodel_id;
|
||
} else if (this.currentContext.kind_type === 1) {
|
||
query.moduleId = this.currentContext.pmodel_id;
|
||
}
|
||
}
|
||
// 参数命名转换:kind_type -> kindType(后端为驼峰)
|
||
if (query.kind_type !== undefined) {
|
||
query.kindType = query.kind_type;
|
||
delete query.kind_type;
|
||
}
|
||
try {
|
||
const { rows, total } = await listDoc(query);
|
||
this.docList = rows || [];
|
||
this.total = total || 0;
|
||
} catch (e) {
|
||
console.error('获取文档列表失败', e);
|
||
this.docList = [];
|
||
this.total = 0;
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
/** 搜索按钮操作 */
|
||
handleQuery() {
|
||
this.queryParams.pageNum = 1;
|
||
this.getList();
|
||
},
|
||
/** 重置按钮操作 */
|
||
resetQuery() {
|
||
this.resetForm("queryForm");
|
||
this.handleQuery();
|
||
},
|
||
// 多选框选中数据
|
||
handleSelectionChange(selection) {
|
||
this.ids = selection.map(item => item.docId);
|
||
this.multiple = !selection.length;
|
||
},
|
||
/** 上传按钮操作 */
|
||
async handleUpload() {
|
||
if (this.currentContext.moduleName) {
|
||
this.uploadTitle = `上传文档 - ${this.currentContext.moduleName}`;
|
||
} else {
|
||
this.uploadTitle = "上传文档";
|
||
}
|
||
// 每次打开均刷新一次项目集合,避免角色/指派变更导致的偶发不显示
|
||
await this.initProjects()
|
||
// 普通用户默认选择“模块文档”,并禁用“项目文档”的单选
|
||
if (this.isNormalUser && !this.isProjectAdmin) {
|
||
this.upload.meta.kindType = 1
|
||
}
|
||
// 预选:优先使用路由上下文;否则选第一项
|
||
if (this.currentContext.projectId) {
|
||
this.upload.meta.projectId = this.currentContext.projectId
|
||
} else {
|
||
// 默认不选任何项目,避免显示成固定ID
|
||
this.upload.meta.projectId = null
|
||
}
|
||
// initProjects 内已处理联动
|
||
this.uploadOpen = true;
|
||
},
|
||
/** 下载按钮操作 */
|
||
async handleDownload(row) {
|
||
const url = process.env.VUE_APP_BASE_API + '/project/doc/download/' + row.docId
|
||
try {
|
||
const res = await fetch(url, { headers: { Authorization: 'Bearer ' + getToken() } })
|
||
const contentType = res.headers.get('content-type') || ''
|
||
if (contentType.includes('application/json')) {
|
||
const j = await res.json()
|
||
this.$message.error(j.msg || '下载失败')
|
||
return
|
||
}
|
||
const blob = await res.blob()
|
||
const a = document.createElement('a')
|
||
a.href = window.URL.createObjectURL(blob)
|
||
a.download = row.docName || 'file'
|
||
document.body.appendChild(a)
|
||
a.click()
|
||
a.remove()
|
||
} catch (e) {
|
||
this.$message.error('下载失败')
|
||
}
|
||
},
|
||
/** 删除按钮操作(支持单条与批量) */
|
||
handleDelete(row) {
|
||
const docIds = row && row.docId ? row.docId : (this.ids && this.ids.length ? this.ids.join(',') : '');
|
||
if (!docIds) {
|
||
this.$modal.msgError('请先选择要删除的文档');
|
||
return;
|
||
}
|
||
this.$modal
|
||
.confirm('是否确认删除文档编号为"' + docIds + '"的数据项?')
|
||
.then(() => delDoc(docIds))
|
||
.then(() => {
|
||
this.$modal.msgSuccess('删除成功');
|
||
this.getList();
|
||
this.ids = [];
|
||
this.multiple = true;
|
||
})
|
||
.catch(() => {});
|
||
},
|
||
// 文件上传中处理
|
||
handleFileUploadProgress(event, file, fileList) {
|
||
this.upload.isUploading = true;
|
||
},
|
||
// 文件上传成功处理
|
||
handleFileSuccess(response, file, fileList) {
|
||
this.upload.open = false;
|
||
this.upload.isUploading = false;
|
||
this.$refs.upload.clearFiles();
|
||
this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
|
||
this.getList();
|
||
},
|
||
// 提交上传文件
|
||
submitFileForm() {
|
||
// 将选择的元数据同步到上传表单字段
|
||
const { kindType, projectId, moduleId } = this.upload.meta;
|
||
this.upload.data.kindType = kindType;
|
||
this.upload.data.projectId = projectId;
|
||
if (kindType === 1) {
|
||
this.upload.data.moduleId = moduleId;
|
||
} else {
|
||
delete this.upload.data.moduleId;
|
||
}
|
||
// 简单校验
|
||
if (!projectId) {
|
||
this.$modal.msgError('请选择所属项目');
|
||
return;
|
||
}
|
||
if (kindType === 1 && !moduleId) {
|
||
this.$modal.msgError('请选择所属模块');
|
||
return;
|
||
}
|
||
this.$refs.upload.submit();
|
||
}
|
||
}
|
||
};
|
||
</script> |