Files
projectSystem/ruoyi-ui/src/views/project/doc/index.vue
2025-09-12 10:35:17 +08:00

492 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>
支持格式PDFDOCDOCXTXT
</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>