任务表单增加阶段选择框 增加审核意见功能 右侧展示优化
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
package com.ruoyi.rm.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.common.core.domain.R;
|
||||||
|
import com.ruoyi.rm.domain.bo.RmFileReviewBo;
|
||||||
|
import com.ruoyi.rm.domain.vo.RmFileReviewVo;
|
||||||
|
import com.ruoyi.rm.service.IRmFileReviewService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/rm/fileReview")
|
||||||
|
public class RmFileReviewController extends BaseController {
|
||||||
|
|
||||||
|
private final IRmFileReviewService fileReviewService;
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
public R<List<RmFileReviewVo>> list(String fileModule, Long fileId) {
|
||||||
|
return R.ok(fileReviewService.queryByFile(fileModule, fileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public R<RmFileReviewVo> add(@RequestBody RmFileReviewBo bo) {
|
||||||
|
return R.ok(fileReviewService.addReview(bo));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ruoyi.rm.domain.bo;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class RmFileReviewBo extends BaseEntity {
|
||||||
|
private Long id;
|
||||||
|
private Long fileId;
|
||||||
|
private String fileModule;
|
||||||
|
private Long reviewerId;
|
||||||
|
private String content;
|
||||||
|
private String reviewAction;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.ruoyi.rm.domain.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.ruoyi.common.core.domain.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("fad_rm_file_review")
|
||||||
|
public class RmFileReview extends BaseEntity implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
private Long fileId;
|
||||||
|
private String fileModule;
|
||||||
|
private Long reviewerId;
|
||||||
|
private String content;
|
||||||
|
private String reviewAction;
|
||||||
|
@TableLogic
|
||||||
|
private Integer delFlag;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.ruoyi.rm.domain.vo;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class RmFileReviewVo implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long fileId;
|
||||||
|
private String fileModule;
|
||||||
|
private Long reviewerId;
|
||||||
|
private String content;
|
||||||
|
private String reviewAction;
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/** 审核人姓名(从 sys_user 关联) */
|
||||||
|
private String reviewerName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.ruoyi.rm.mapper;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.mapper.BaseMapperPlus;
|
||||||
|
import com.ruoyi.rm.domain.entity.RmFileReview;
|
||||||
|
import com.ruoyi.rm.domain.vo.RmFileReviewVo;
|
||||||
|
|
||||||
|
public interface RmFileReviewMapper extends BaseMapperPlus<RmFileReviewMapper, RmFileReview, RmFileReviewVo> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.ruoyi.rm.service;
|
||||||
|
|
||||||
|
import com.ruoyi.rm.domain.bo.RmFileReviewBo;
|
||||||
|
import com.ruoyi.rm.domain.vo.RmFileReviewVo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IRmFileReviewService {
|
||||||
|
|
||||||
|
List<RmFileReviewVo> queryByFile(String fileModule, Long fileId);
|
||||||
|
|
||||||
|
RmFileReviewVo addReview(RmFileReviewBo bo);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.ruoyi.rm.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||||
|
import com.ruoyi.common.helper.LoginHelper;
|
||||||
|
import com.ruoyi.rm.domain.bo.RmFileReviewBo;
|
||||||
|
import com.ruoyi.rm.domain.entity.RmFileReview;
|
||||||
|
import com.ruoyi.rm.domain.vo.RmFileReviewVo;
|
||||||
|
import com.ruoyi.rm.mapper.RmFileReviewMapper;
|
||||||
|
import com.ruoyi.rm.service.IRmFileReviewService;
|
||||||
|
import com.ruoyi.system.mapper.SysUserMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class RmFileReviewServiceImpl implements IRmFileReviewService {
|
||||||
|
|
||||||
|
private final RmFileReviewMapper baseMapper;
|
||||||
|
private final SysUserMapper sysUserMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RmFileReviewVo> queryByFile(String fileModule, Long fileId) {
|
||||||
|
List<RmFileReviewVo> list = baseMapper.selectVoList(
|
||||||
|
Wrappers.<RmFileReview>lambdaQuery()
|
||||||
|
.eq(RmFileReview::getFileModule, fileModule)
|
||||||
|
.eq(RmFileReview::getFileId, fileId)
|
||||||
|
.orderByAsc(RmFileReview::getCreateTime));
|
||||||
|
for (RmFileReviewVo vo : list) {
|
||||||
|
if (vo.getReviewerId() != null) {
|
||||||
|
SysUser user = sysUserMapper.selectUserById(vo.getReviewerId());
|
||||||
|
if (user != null) {
|
||||||
|
vo.setReviewerName(user.getNickName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RmFileReviewVo addReview(RmFileReviewBo bo) {
|
||||||
|
Long userId = LoginHelper.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
throw new RuntimeException("未登录");
|
||||||
|
}
|
||||||
|
RmFileReview entity = BeanUtil.toBean(bo, RmFileReview.class);
|
||||||
|
entity.setReviewerId(userId);
|
||||||
|
baseMapper.insert(entity);
|
||||||
|
|
||||||
|
RmFileReviewVo vo = BeanUtil.toBean(entity, RmFileReviewVo.class);
|
||||||
|
SysUser user = sysUserMapper.selectUserById(userId);
|
||||||
|
vo.setReviewerName(user != null ? user.getNickName() : null);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,3 +122,21 @@ export function delTask(taskId) {
|
|||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按总包项目+阶段查询任务
|
||||||
|
export function listRmTask(projectId, stageCode) {
|
||||||
|
return request({
|
||||||
|
url: '/oa/task/rm/tasks',
|
||||||
|
method: 'get',
|
||||||
|
params: { projectId, stageCode }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按总包项目统计各阶段任务数
|
||||||
|
export function taskCountByStage(projectId) {
|
||||||
|
return request({
|
||||||
|
url: '/oa/task/rm/taskCount',
|
||||||
|
method: 'get',
|
||||||
|
params: { projectId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
17
ruoyi-ui/src/api/rm/fileReview.js
Normal file
17
ruoyi-ui/src/api/rm/fileReview.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function listFileReview(fileModule, fileId) {
|
||||||
|
return request({
|
||||||
|
url: '/rm/fileReview/list',
|
||||||
|
method: 'get',
|
||||||
|
params: { fileModule, fileId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addFileReview(data) {
|
||||||
|
return request({
|
||||||
|
url: '/rm/fileReview',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -177,6 +177,26 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="阶段" prop="stageCode">
|
||||||
|
<el-select v-model="form.stageCode" placeholder="请选择阶段" clearable style="width:100%">
|
||||||
|
<el-option label="安装前准备" value="install_prep" />
|
||||||
|
<el-option label="布局图确定" value="layout" />
|
||||||
|
<el-option label="图纸详细设计" value="drawing_design" />
|
||||||
|
<el-option label="设备说明书" value="manual" />
|
||||||
|
<el-option label="技术审查" value="tech_review" />
|
||||||
|
<el-option label="预算" value="budget" />
|
||||||
|
<el-option label="采购管理" value="purchase" />
|
||||||
|
<el-option label="设备制造" value="manufacture" />
|
||||||
|
<el-option label="发货前清单" value="shipping" />
|
||||||
|
<el-option label="安装前准备详" value="install_prep_detail" />
|
||||||
|
<el-option label="安装问题反馈" value="install_feedback" />
|
||||||
|
<el-option label="安装后验收" value="acceptance" />
|
||||||
|
<el-option label="热负荷试车" value="hot_commissioning" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
<el-col :span="12" v-loading="trackLoading">
|
<el-col :span="12" v-loading="trackLoading">
|
||||||
<el-form-item label="项目进度" prop="trackId">
|
<el-form-item label="项目进度" prop="trackId">
|
||||||
<el-cascader v-if="trackOptions" v-model="form.trackId" :options="trackOptions" :props="cascaderProps"
|
<el-cascader v-if="trackOptions" v-model="form.trackId" :options="trackOptions" :props="cascaderProps"
|
||||||
@@ -631,7 +651,6 @@ export default {
|
|||||||
this.userLoading = false;
|
this.userLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/** 查询项目管理列表 */
|
|
||||||
getList () {
|
getList () {
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -735,7 +754,8 @@ export default {
|
|||||||
// 表单重置
|
// 表单重置
|
||||||
reset () {
|
reset () {
|
||||||
this.form = {
|
this.form = {
|
||||||
status: 0
|
status: 0,
|
||||||
|
stageCode: undefined
|
||||||
};
|
};
|
||||||
this.resetForm("form");
|
this.resetForm("form");
|
||||||
this.fileList = [];
|
this.fileList = [];
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: detail + preview -->
|
<!-- Right: info + review + preview -->
|
||||||
<div class="right-panel">
|
<div class="right-panel">
|
||||||
<div class="right-header">
|
<div class="right-header">
|
||||||
<span>{{ selected ? selected.drawingName : '选择图纸预览' }}</span>
|
<span>{{ selected ? selected.drawingName : '选择图纸预览' }}</span>
|
||||||
@@ -62,23 +62,69 @@
|
|||||||
<el-button size="small" icon="el-icon-edit" @click="handleEdit(selected)">编辑</el-button>
|
<el-button size="small" icon="el-icon-edit" @click="handleEdit(selected)">编辑</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Info card -->
|
<!-- Collapse: info + review -->
|
||||||
<div v-if="selected" class="detail-card">
|
<el-collapse v-if="selected" v-model="collapseActive" class="info-collapse">
|
||||||
<el-row :gutter="16" class="info-row">
|
<el-collapse-item title="图纸信息" name="detail">
|
||||||
<el-col :span="8"><span class="info-label">图号</span><span class="info-value">{{ selected.drawingNo || '-' }}</span></el-col>
|
<div class="detail-grid">
|
||||||
<el-col :span="8"><span class="info-label">图纸类型</span><span class="info-value">{{ selected.drawingType || '-' }}</span></el-col>
|
<div class="detail-item"><span class="detail-label">图号</span><span class="detail-value">{{ selected.drawingNo || '-' }}</span></div>
|
||||||
<el-col :span="8"><span class="info-label">版本</span><span class="info-value">{{ selected.version || '-' }}</span></el-col>
|
<div class="detail-item"><span class="detail-label">图纸类型</span><span class="detail-value">{{ selected.drawingType || '-' }}</span></div>
|
||||||
</el-row>
|
<div class="detail-item"><span class="detail-label">版本</span><span class="detail-value">{{ selected.version || '-' }}</span></div>
|
||||||
<el-row :gutter="16" class="info-row">
|
<div class="detail-item"><span class="detail-label">设计人</span><span class="detail-value">{{ selected.drawer || '-' }}</span></div>
|
||||||
<el-col :span="8"><span class="info-label">设计人</span><span class="info-value">{{ selected.drawer || '-' }}</span></el-col>
|
<div class="detail-item"><span class="detail-label">设计日期</span><span class="detail-value">{{ selected.startDate || '-' }}</span></div>
|
||||||
<el-col :span="8"><span class="info-label">设计日期</span><span class="info-value">{{ selected.startDate || '-' }}</span></el-col>
|
<div class="detail-item"><span class="detail-label">完成日期</span><span class="detail-value">{{ selected.endDate || '-' }}</span></div>
|
||||||
<el-col :span="8"><span class="info-label">完成日期</span><span class="info-value">{{ selected.endDate || '-' }}</span></el-col>
|
<div class="detail-item"><span class="detail-label">状态</span><span class="detail-value"><el-tag :type="statusTag(selected.status)" size="mini">{{ statusLabel(selected.status) }}</el-tag></span></div>
|
||||||
</el-row>
|
<div class="detail-item" v-if="selected.remark"><span class="detail-label">备注</span><span class="detail-value">{{ selected.remark }}</span></div>
|
||||||
<el-row :gutter="16" class="info-row">
|
|
||||||
<el-col :span="8"><span class="info-label">状态</span><span class="info-value"><el-tag :type="statusTag(selected.status)" size="mini">{{ statusLabel(selected.status) }}</el-tag></span></el-col>
|
|
||||||
<el-col :span="16"><span class="info-label">备注</span><span class="info-value text-ellipsis" :title="selected.remark">{{ selected.remark || '-' }}</span></el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item name="review">
|
||||||
|
<template slot="title">
|
||||||
|
<span>审核意见 <el-tag v-if="fileReviews.length > 0" size="mini" style="margin-left:4px">{{ fileReviews.length }}</el-tag></span>
|
||||||
|
</template>
|
||||||
|
<el-button size="small" type="primary" icon="el-icon-edit" @click="openReviewDialog" style="margin-bottom:8px">发表意见</el-button>
|
||||||
|
<el-timeline v-if="fileReviews.length > 0">
|
||||||
|
<el-timeline-item
|
||||||
|
v-for="item in fileReviews"
|
||||||
|
:key="item.id"
|
||||||
|
:timestamp="item.createTime"
|
||||||
|
placement="top">
|
||||||
|
<div class="review-item">
|
||||||
|
<div class="review-item-header">
|
||||||
|
<strong>{{ item.reviewerName }}</strong>
|
||||||
|
<el-tag v-if="item.reviewAction" size="mini" :type="reviewActionTag(item.reviewAction)" class="review-action-tag">{{ item.reviewAction }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<p class="review-item-content">{{ item.content }}</p>
|
||||||
|
</div>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
<div v-else class="review-empty">
|
||||||
|
<i class="el-icon-chat-dot-round"></i>
|
||||||
|
<span>暂无审核意见</span>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item name="tasks">
|
||||||
|
<template slot="title">
|
||||||
|
<span>关联任务 <el-tag v-if="rmTasks.length > 0" size="mini" style="margin-left:4px">{{ rmTasks.length }}</el-tag></span>
|
||||||
|
</template>
|
||||||
|
<div v-if="rmTasks.length > 0" class="task-list">
|
||||||
|
<div v-for="t in rmTasks" :key="t.taskId" class="task-item">
|
||||||
|
<div class="task-item-header">
|
||||||
|
<span class="task-item-title">{{ t.taskTitle }}</span>
|
||||||
|
<el-tag v-if="t.state === 2" size="mini" type="success">完成</el-tag>
|
||||||
|
<el-tag v-else-if="t.state === 1" size="mini" type="warning">待验收</el-tag>
|
||||||
|
<el-tag v-else size="mini" type="info">进行中</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="task-item-meta" v-if="t.workerNickName || t.createUserNickName">
|
||||||
|
<span v-if="t.workerNickName">执行人: {{ t.workerNickName }}</span>
|
||||||
|
<span v-if="t.createUserNickName">发起人: {{ t.createUserNickName }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="review-empty">
|
||||||
|
<i class="el-icon-s-management"></i>
|
||||||
|
<span>暂无关联任务</span>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
<!-- Viewer -->
|
<!-- Viewer -->
|
||||||
<div class="viewer-area" ref="viewerContainer">
|
<div class="viewer-area" ref="viewerContainer">
|
||||||
<div v-if="!selected || !selected.fileUrl" class="viewer-placeholder">
|
<div v-if="!selected || !selected.fileUrl" class="viewer-placeholder">
|
||||||
@@ -89,6 +135,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Review Dialog -->
|
||||||
|
<el-dialog title="发表审核意见" :visible.sync="reviewDialogVisible" width="450px" append-to-body>
|
||||||
|
<el-form ref="reviewFormRef" :model="reviewForm" :rules="reviewRules" label-width="80px" size="small">
|
||||||
|
<el-form-item label="审核意见" prop="reviewAction">
|
||||||
|
<el-select v-model="reviewForm.reviewAction" placeholder="请选择" style="width:100%">
|
||||||
|
<el-option label="同意" value="同意" />
|
||||||
|
<el-option label="驳回" value="驳回" />
|
||||||
|
<el-option label="待定" value="待定" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="详细内容" prop="content">
|
||||||
|
<el-input v-model="reviewForm.content" type="textarea" :rows="4" placeholder="请输入审核意见" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button size="small" @click="reviewDialogVisible = false">取消</el-button>
|
||||||
|
<el-button size="small" type="primary" :loading="reviewSubmitting" @click="submitReview">提交</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<!-- Add/Edit Dialog -->
|
<!-- Add/Edit Dialog -->
|
||||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" append-to-body>
|
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" append-to-body>
|
||||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" size="small">
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" size="small">
|
||||||
@@ -185,6 +251,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { listDrawingDesign, getDrawingDesign, addDrawingDesign, updateDrawingDesign, delDrawingDesign } from '@/api/rm/drawingDesign'
|
import { listDrawingDesign, getDrawingDesign, addDrawingDesign, updateDrawingDesign, delDrawingDesign } from '@/api/rm/drawingDesign'
|
||||||
import { listProject } from '@/api/rm/project'
|
import { listProject } from '@/api/rm/project'
|
||||||
|
import { listFileReview, addFileReview } from '@/api/rm/fileReview'
|
||||||
|
import { listRmTask } from '@/api/oa/task'
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
import { mountViewerFrame } from '@flyfish-group/file-viewer-web'
|
import { mountViewerFrame } from '@flyfish-group/file-viewer-web'
|
||||||
|
|
||||||
@@ -206,7 +274,16 @@ export default {
|
|||||||
uploadUrl: process.env.VUE_APP_BASE_API + '/common/upload',
|
uploadUrl: process.env.VUE_APP_BASE_API + '/common/upload',
|
||||||
uploadHeaders: { Authorization: 'Bearer ' + getToken() },
|
uploadHeaders: { Authorization: 'Bearer ' + getToken() },
|
||||||
selected: null,
|
selected: null,
|
||||||
viewerCtrl: null
|
viewerCtrl: null,
|
||||||
|
collapseActive: [],
|
||||||
|
fileReviews: [],
|
||||||
|
rmTasks: [],
|
||||||
|
reviewDialogVisible: false,
|
||||||
|
reviewForm: { reviewAction: '', content: '' },
|
||||||
|
reviewRules: {
|
||||||
|
reviewAction: [{ required: true, message: '请选择审核意见', trigger: 'change' }]
|
||||||
|
},
|
||||||
|
reviewSubmitting: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() { this.loadCurrentProject() },
|
created() { this.loadCurrentProject() },
|
||||||
@@ -214,12 +291,19 @@ export default {
|
|||||||
this.destroyViewer()
|
this.destroyViewer()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
loadRmTasks(projectId) {
|
||||||
|
if (!projectId) return
|
||||||
|
listRmTask(projectId, 'drawing_design').then(res => {
|
||||||
|
this.rmTasks = res.data || []
|
||||||
|
})
|
||||||
|
},
|
||||||
loadCurrentProject() {
|
loadCurrentProject() {
|
||||||
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
const pid = this.$route.query.projectId || sessionStorage.getItem('rm_current_project_id')
|
||||||
if (pid) {
|
if (pid) {
|
||||||
this.currentProjectId = pid
|
this.currentProjectId = pid
|
||||||
this.query.projectId = pid
|
this.query.projectId = pid
|
||||||
this.loadList()
|
this.loadList()
|
||||||
|
this.loadRmTasks(pid)
|
||||||
} else {
|
} else {
|
||||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||||
const rows = res.rows || []
|
const rows = res.rows || []
|
||||||
@@ -227,6 +311,7 @@ export default {
|
|||||||
this.currentProjectId = rows[0].projectId
|
this.currentProjectId = rows[0].projectId
|
||||||
this.query.projectId = rows[0].projectId
|
this.query.projectId = rows[0].projectId
|
||||||
this.loadList()
|
this.loadList()
|
||||||
|
this.loadRmTasks(rows[0].projectId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -247,6 +332,7 @@ export default {
|
|||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.mountPreview(row)
|
this.mountPreview(row)
|
||||||
})
|
})
|
||||||
|
this.loadReviews('drawing_design', row.drawingId)
|
||||||
},
|
},
|
||||||
mountPreview(row) {
|
mountPreview(row) {
|
||||||
this.destroyViewer()
|
this.destroyViewer()
|
||||||
@@ -284,6 +370,7 @@ export default {
|
|||||||
if (this.selected && this.selected.drawingId === row.drawingId) {
|
if (this.selected && this.selected.drawingId === row.drawingId) {
|
||||||
this.destroyViewer()
|
this.destroyViewer()
|
||||||
this.selected = null
|
this.selected = null
|
||||||
|
this.fileReviews = []
|
||||||
}
|
}
|
||||||
this.loadList()
|
this.loadList()
|
||||||
})
|
})
|
||||||
@@ -319,7 +406,37 @@ export default {
|
|||||||
downloadFile(row) { if (row.fileUrl) window.open(row.fileUrl, '_blank') },
|
downloadFile(row) { if (row.fileUrl) window.open(row.fileUrl, '_blank') },
|
||||||
fileNameFromUrl(url) { if (!url) return ''; return decodeURIComponent(url.split('/').pop() || '') },
|
fileNameFromUrl(url) { if (!url) return ''; return decodeURIComponent(url.split('/').pop() || '') },
|
||||||
statusTag(s) { return { in_progress: 'info', completed: 'success', reviewed: 'primary' }[s] || 'info' },
|
statusTag(s) { return { in_progress: 'info', completed: 'success', reviewed: 'primary' }[s] || 'info' },
|
||||||
statusLabel(s) { return { in_progress: '进行中', completed: '已完成', reviewed: '已审查' }[s] || s }
|
statusLabel(s) { return { in_progress: '进行中', completed: '已完成', reviewed: '已审查' }[s] || s },
|
||||||
|
loadReviews(fileModule, fileId) {
|
||||||
|
if (!fileId) return
|
||||||
|
listFileReview(fileModule, fileId).then(res => {
|
||||||
|
this.fileReviews = res.data || []
|
||||||
|
})
|
||||||
|
},
|
||||||
|
openReviewDialog() {
|
||||||
|
this.reviewForm = { reviewAction: '', content: '' }
|
||||||
|
this.reviewDialogVisible = true
|
||||||
|
this.$nextTick(() => { this.$refs.reviewFormRef?.clearValidate() })
|
||||||
|
},
|
||||||
|
submitReview() {
|
||||||
|
this.$refs.reviewFormRef.validate(valid => {
|
||||||
|
if (!valid) return
|
||||||
|
this.reviewSubmitting = true
|
||||||
|
addFileReview({
|
||||||
|
fileModule: 'drawing_design',
|
||||||
|
fileId: this.selected.drawingId,
|
||||||
|
content: this.reviewForm.content,
|
||||||
|
reviewAction: this.reviewForm.reviewAction
|
||||||
|
}).then(() => {
|
||||||
|
this.$message.success('提交成功')
|
||||||
|
this.reviewDialogVisible = false
|
||||||
|
this.loadReviews('drawing_design', this.selected.drawingId)
|
||||||
|
}).finally(() => { this.reviewSubmitting = false })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
reviewActionTag(action) {
|
||||||
|
return { '同意': 'success', '驳回': 'danger', '待定': 'warning' }[action] || 'info'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -340,12 +457,26 @@ export default {
|
|||||||
.right-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
|
.right-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
|
||||||
.right-actions { display: flex; gap: 4px; }
|
.right-actions { display: flex; gap: 4px; }
|
||||||
|
|
||||||
/* Detail card */
|
/* Collapse info + review */
|
||||||
.detail-card { padding: 10px 12px; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; }
|
.info-collapse { flex-shrink: 0; }
|
||||||
.info-row { margin-bottom: 6px; }
|
.info-collapse >>> .el-collapse-item__header { padding: 0 12px; font-size: 13px; font-weight: 600; height: 36px; }
|
||||||
.info-label { font-size: 11px; color: #909399; margin-right: 6px; }
|
.info-collapse >>> .el-collapse-item__wrap { border-bottom: 1px solid #e8eaed; }
|
||||||
.info-value { font-size: 12px; color: #333; }
|
.info-collapse >>> .el-collapse-item__content { padding: 8px 12px 10px; }
|
||||||
.text-ellipsis { display: inline-block; max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
|
.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 16px; }
|
||||||
|
.detail-item { display: flex; font-size: 12px; line-height: 1.8; }
|
||||||
|
.detail-label { color: #909399; width: 55px; flex-shrink: 0; }
|
||||||
|
.detail-value { color: #333; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.review-item-header { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; }
|
||||||
|
.review-item-header strong { font-size: 13px; }
|
||||||
|
.review-item-content { font-size: 12px; color: #555; margin: 2px 0 0; line-height: 1.5; white-space: pre-wrap; }
|
||||||
|
.review-empty { text-align: center; color: #c0c4cc; font-size: 13px; padding: 8px 0; display: flex; flex-direction: column; align-items: center; gap: 4px; }
|
||||||
|
.review-action-tag { font-size: 11px; }
|
||||||
|
.info-collapse >>> .el-timeline-item__timestamp { font-size: 12px; color: #999; }
|
||||||
|
.task-list { display: flex; flex-direction: column; gap: 6px; }
|
||||||
|
.task-item { background: #f8f9fa; border-radius: 4px; padding: 6px 8px; }
|
||||||
|
.task-item-header { display: flex; align-items: center; justify-content: space-between; gap: 4px; }
|
||||||
|
.task-item-title { font-size: 12px; font-weight: 600; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
||||||
|
.task-item-meta { font-size: 11px; color: #909399; margin-top: 2px; display: flex; gap: 8px; }
|
||||||
|
|
||||||
/* Viewer */
|
/* Viewer */
|
||||||
.viewer-area { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; }
|
.viewer-area { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; }
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
@size-change="handleSizeChange" @current-change="handlePageChange" />
|
@size-change="handleSizeChange" @current-change="handlePageChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Right: preview -->
|
<!-- Right: details + review + preview -->
|
||||||
<div class="right-panel">
|
<div class="right-panel">
|
||||||
<div class="right-header">
|
<div class="right-header">
|
||||||
<span>{{ selectedFile ? selectedFile.fileName : '选择文件预览' }}</span>
|
<span>{{ selectedFile ? selectedFile.fileName : '选择文件预览' }}</span>
|
||||||
@@ -56,6 +56,88 @@
|
|||||||
<el-button size="small" icon="el-icon-download" @click="downloadFile(selectedFile)">下载</el-button>
|
<el-button size="small" icon="el-icon-download" @click="downloadFile(selectedFile)">下载</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Collapse: info + review -->
|
||||||
|
<el-collapse v-if="selectedFile" v-model="collapseActive" class="info-collapse">
|
||||||
|
<el-collapse-item title="文件信息" name="detail">
|
||||||
|
<div class="detail-grid">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">文件名</span>
|
||||||
|
<span class="detail-value">{{ selectedFile.fileName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">类型</span>
|
||||||
|
<span class="detail-value">{{ selectedFile.fileType }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">版本</span>
|
||||||
|
<span class="detail-value">{{ selectedFile.version || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">上传日期</span>
|
||||||
|
<span class="detail-value">{{ selectedFile.uploadDate || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">状态</span>
|
||||||
|
<span class="detail-value">
|
||||||
|
<el-tag :type="statusTag(selectedFile.status)" size="mini">{{ statusLabel(selectedFile.status) }}</el-tag>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item" v-if="selectedFile.remark">
|
||||||
|
<span class="detail-label">备注</span>
|
||||||
|
<span class="detail-value">{{ selectedFile.remark }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item name="review">
|
||||||
|
<template slot="title">
|
||||||
|
<span>审核意见 <el-tag v-if="fileReviews.length > 0" size="mini" style="margin-left:4px">{{ fileReviews.length }}</el-tag></span>
|
||||||
|
</template>
|
||||||
|
<el-button size="small" type="primary" icon="el-icon-edit" @click="openReviewDialog" style="margin-bottom:8px">发表意见</el-button>
|
||||||
|
<el-timeline v-if="fileReviews.length > 0">
|
||||||
|
<el-timeline-item
|
||||||
|
v-for="item in fileReviews"
|
||||||
|
:key="item.id"
|
||||||
|
:timestamp="item.createTime"
|
||||||
|
placement="top">
|
||||||
|
<div class="review-item">
|
||||||
|
<div class="review-item-header">
|
||||||
|
<strong>{{ item.reviewerName }}</strong>
|
||||||
|
<el-tag v-if="item.reviewAction" size="mini" :type="reviewActionTag(item.reviewAction)" class="review-action-tag">{{ item.reviewAction }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<p class="review-item-content">{{ item.content }}</p>
|
||||||
|
</div>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
<div v-else class="review-empty">
|
||||||
|
<i class="el-icon-chat-dot-round"></i>
|
||||||
|
<span>暂无审核意见</span>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item name="tasks">
|
||||||
|
<template slot="title">
|
||||||
|
<span>关联任务 <el-tag v-if="rmTasks.length > 0" size="mini" style="margin-left:4px">{{ rmTasks.length }}</el-tag></span>
|
||||||
|
</template>
|
||||||
|
<div v-if="rmTasks.length > 0" class="task-list">
|
||||||
|
<div v-for="t in rmTasks" :key="t.taskId" class="task-item">
|
||||||
|
<div class="task-item-header">
|
||||||
|
<span class="task-item-title">{{ t.taskTitle }}</span>
|
||||||
|
<el-tag v-if="t.state === 2" size="mini" type="success">完成</el-tag>
|
||||||
|
<el-tag v-else-if="t.state === 1" size="mini" type="warning">待验收</el-tag>
|
||||||
|
<el-tag v-else size="mini" type="info">进行中</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="task-item-meta" v-if="t.workerNickName || t.createUserNickName">
|
||||||
|
<span v-if="t.workerNickName">执行人: {{ t.workerNickName }}</span>
|
||||||
|
<span v-if="t.createUserNickName">发起人: {{ t.createUserNickName }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="review-empty">
|
||||||
|
<i class="el-icon-s-management"></i>
|
||||||
|
<span>暂无关联任务</span>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
<!-- Viewer -->
|
||||||
<div class="viewer-area" ref="viewerContainer">
|
<div class="viewer-area" ref="viewerContainer">
|
||||||
<div v-if="!selectedFile || !selectedFile.fileUrl" class="viewer-placeholder">
|
<div v-if="!selectedFile || !selectedFile.fileUrl" class="viewer-placeholder">
|
||||||
<i class="el-icon-view" style="font-size:48px;color:#c0c4cc;"></i>
|
<i class="el-icon-view" style="font-size:48px;color:#c0c4cc;"></i>
|
||||||
@@ -131,12 +213,34 @@
|
|||||||
<el-button size="small" type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
<el-button size="small" type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- Review Dialog -->
|
||||||
|
<el-dialog title="发表审核意见" :visible.sync="reviewDialogVisible" width="450px" append-to-body>
|
||||||
|
<el-form ref="reviewFormRef" :model="reviewForm" :rules="reviewRules" label-width="80px" size="small">
|
||||||
|
<el-form-item label="审核意见" prop="reviewAction">
|
||||||
|
<el-select v-model="reviewForm.reviewAction" placeholder="请选择" style="width:100%">
|
||||||
|
<el-option label="同意" value="同意" />
|
||||||
|
<el-option label="驳回" value="驳回" />
|
||||||
|
<el-option label="待定" value="待定" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="详细内容" prop="content">
|
||||||
|
<el-input v-model="reviewForm.content" type="textarea" :rows="4" placeholder="请输入审核意见" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button size="small" @click="reviewDialogVisible = false">取消</el-button>
|
||||||
|
<el-button size="small" type="primary" :loading="reviewSubmitting" @click="submitReview">提交</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listLayoutFile, getLayoutFile, addLayoutFile, updateLayoutFile, delLayoutFile } from '@/api/rm/layoutFile'
|
import { listLayoutFile, getLayoutFile, addLayoutFile, updateLayoutFile, delLayoutFile } from '@/api/rm/layoutFile'
|
||||||
import { listProject } from '@/api/rm/project'
|
import { listProject } from '@/api/rm/project'
|
||||||
|
import { listFileReview, addFileReview } from '@/api/rm/fileReview'
|
||||||
|
import { listRmTask } from '@/api/oa/task'
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
import { mountViewerFrame } from '@flyfish-group/file-viewer-web'
|
import { mountViewerFrame } from '@flyfish-group/file-viewer-web'
|
||||||
|
|
||||||
@@ -158,7 +262,16 @@ export default {
|
|||||||
uploadUrl: process.env.VUE_APP_BASE_API + '/common/upload',
|
uploadUrl: process.env.VUE_APP_BASE_API + '/common/upload',
|
||||||
uploadHeaders: { Authorization: 'Bearer ' + getToken() },
|
uploadHeaders: { Authorization: 'Bearer ' + getToken() },
|
||||||
selectedFile: null,
|
selectedFile: null,
|
||||||
viewerCtrl: null
|
viewerCtrl: null,
|
||||||
|
collapseActive: [],
|
||||||
|
fileReviews: [],
|
||||||
|
rmTasks: [],
|
||||||
|
reviewDialogVisible: false,
|
||||||
|
reviewForm: { reviewAction: '', content: '' },
|
||||||
|
reviewRules: {
|
||||||
|
reviewAction: [{ required: true, message: '请选择审核意见', trigger: 'change' }]
|
||||||
|
},
|
||||||
|
reviewSubmitting: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() { this.loadCurrentProject() },
|
created() { this.loadCurrentProject() },
|
||||||
@@ -172,6 +285,7 @@ export default {
|
|||||||
this.currentProjectId = pid
|
this.currentProjectId = pid
|
||||||
this.query.projectId = pid
|
this.query.projectId = pid
|
||||||
this.loadList()
|
this.loadList()
|
||||||
|
this.loadRmTasks(pid)
|
||||||
} else {
|
} else {
|
||||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||||
const rows = res.rows || []
|
const rows = res.rows || []
|
||||||
@@ -179,10 +293,17 @@ export default {
|
|||||||
this.currentProjectId = rows[0].projectId
|
this.currentProjectId = rows[0].projectId
|
||||||
this.query.projectId = rows[0].projectId
|
this.query.projectId = rows[0].projectId
|
||||||
this.loadList()
|
this.loadList()
|
||||||
|
this.loadRmTasks(rows[0].projectId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
loadRmTasks(projectId) {
|
||||||
|
if (!projectId) return
|
||||||
|
listRmTask(projectId, 'layout').then(res => {
|
||||||
|
this.rmTasks = res.data || []
|
||||||
|
})
|
||||||
|
},
|
||||||
loadList() {
|
loadList() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
listLayoutFile(this.query).then(res => {
|
listLayoutFile(this.query).then(res => {
|
||||||
@@ -199,6 +320,13 @@ export default {
|
|||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.mountPreview(row)
|
this.mountPreview(row)
|
||||||
})
|
})
|
||||||
|
this.loadReviews('layout', row.layoutFileId)
|
||||||
|
},
|
||||||
|
loadReviews(fileModule, fileId) {
|
||||||
|
if (!fileId) return
|
||||||
|
listFileReview(fileModule, fileId).then(res => {
|
||||||
|
this.fileReviews = res.data || []
|
||||||
|
})
|
||||||
},
|
},
|
||||||
mountPreview(row) {
|
mountPreview(row) {
|
||||||
this.destroyViewer()
|
this.destroyViewer()
|
||||||
@@ -236,6 +364,7 @@ export default {
|
|||||||
if (this.selectedFile && this.selectedFile.layoutFileId === row.layoutFileId) {
|
if (this.selectedFile && this.selectedFile.layoutFileId === row.layoutFileId) {
|
||||||
this.destroyViewer()
|
this.destroyViewer()
|
||||||
this.selectedFile = null
|
this.selectedFile = null
|
||||||
|
this.fileReviews = []
|
||||||
}
|
}
|
||||||
this.loadList()
|
this.loadList()
|
||||||
})
|
})
|
||||||
@@ -278,7 +407,31 @@ export default {
|
|||||||
downloadFile(row) { if (row.fileUrl) window.open(row.fileUrl, '_blank') },
|
downloadFile(row) { if (row.fileUrl) window.open(row.fileUrl, '_blank') },
|
||||||
fileNameFromUrl(url) { if (!url) return ''; return decodeURIComponent(url.split('/').pop() || '') },
|
fileNameFromUrl(url) { if (!url) return ''; return decodeURIComponent(url.split('/').pop() || '') },
|
||||||
statusTag(s) { return { pending: 'info', approved: 'success' }[s] || 'info' },
|
statusTag(s) { return { pending: 'info', approved: 'success' }[s] || 'info' },
|
||||||
statusLabel(s) { return { pending: '待审核', approved: '已批准' }[s] || s }
|
statusLabel(s) { return { pending: '待审核', approved: '已批准' }[s] || s },
|
||||||
|
openReviewDialog() {
|
||||||
|
this.reviewForm = { reviewAction: '', content: '' }
|
||||||
|
this.reviewDialogVisible = true
|
||||||
|
this.$nextTick(() => { this.$refs.reviewFormRef?.clearValidate() })
|
||||||
|
},
|
||||||
|
submitReview() {
|
||||||
|
this.$refs.reviewFormRef.validate(valid => {
|
||||||
|
if (!valid) return
|
||||||
|
this.reviewSubmitting = true
|
||||||
|
addFileReview({
|
||||||
|
fileModule: 'layout',
|
||||||
|
fileId: this.selectedFile.layoutFileId,
|
||||||
|
content: this.reviewForm.content,
|
||||||
|
reviewAction: this.reviewForm.reviewAction
|
||||||
|
}).then(() => {
|
||||||
|
this.$message.success('提交成功')
|
||||||
|
this.reviewDialogVisible = false
|
||||||
|
this.loadReviews('layout', this.selectedFile.layoutFileId)
|
||||||
|
}).finally(() => { this.reviewSubmitting = false })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
reviewActionTag(action) {
|
||||||
|
return { '同意': 'success', '驳回': 'danger', '待定': 'warning' }[action] || 'info'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -304,7 +457,30 @@ export default {
|
|||||||
.right-panel { flex: 1; background: #fff; border-radius: 4px; border: 1px solid #d0d7de; display: flex; flex-direction: column; min-width: 0; }
|
.right-panel { flex: 1; background: #fff; border-radius: 4px; border: 1px solid #d0d7de; display: flex; flex-direction: column; min-width: 0; }
|
||||||
.right-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
|
.right-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
|
||||||
.right-actions { display: flex; gap: 4px; }
|
.right-actions { display: flex; gap: 4px; }
|
||||||
.viewer-area { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; }
|
|
||||||
|
/* Collapse info + review */
|
||||||
|
.info-collapse { flex-shrink: 0; }
|
||||||
|
.info-collapse >>> .el-collapse-item__header { padding: 0 12px; font-size: 13px; font-weight: 600; height: 36px; }
|
||||||
|
.info-collapse >>> .el-collapse-item__wrap { border-bottom: 1px solid #e8eaed; }
|
||||||
|
.info-collapse >>> .el-collapse-item__content { padding: 8px 12px 10px; }
|
||||||
|
.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 16px; }
|
||||||
|
.detail-item { display: flex; font-size: 12px; line-height: 1.8; }
|
||||||
|
.detail-label { color: #909399; width: 65px; flex-shrink: 0; }
|
||||||
|
.detail-value { color: #333; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.review-item-header { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; }
|
||||||
|
.review-item-header strong { font-size: 13px; }
|
||||||
|
.review-item-content { font-size: 12px; color: #555; margin: 2px 0 0; line-height: 1.5; white-space: pre-wrap; }
|
||||||
|
.review-empty { text-align: center; color: #c0c4cc; font-size: 13px; padding: 8px 0; display: flex; flex-direction: column; align-items: center; gap: 4px; }
|
||||||
|
.review-action-tag { font-size: 11px; }
|
||||||
|
.info-collapse >>> .el-timeline-item__timestamp { font-size: 12px; color: #999; }
|
||||||
|
.task-list { display: flex; flex-direction: column; gap: 6px; }
|
||||||
|
.task-item { background: #f8f9fa; border-radius: 4px; padding: 6px 8px; }
|
||||||
|
.task-item-header { display: flex; align-items: center; justify-content: space-between; gap: 4px; }
|
||||||
|
.task-item-title { font-size: 12px; font-weight: 600; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
||||||
|
.task-item-meta { font-size: 11px; color: #909399; margin-top: 2px; display: flex; gap: 8px; }
|
||||||
|
|
||||||
|
/* Viewer */
|
||||||
|
.viewer-area { flex: 1; min-height: 200px; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; }
|
||||||
.viewer-area > iframe { width: 100%; height: 100%; border: 0; }
|
.viewer-area > iframe { width: 100%; height: 100%; border: 0; }
|
||||||
.viewer-placeholder { text-align: center; }
|
.viewer-placeholder { text-align: center; }
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: detail + preview -->
|
<!-- Right: info + review + preview -->
|
||||||
<div class="right-panel">
|
<div class="right-panel">
|
||||||
<div class="right-header">
|
<div class="right-header">
|
||||||
<span>{{ selected ? selected.manualName : '选择文件预览' }}</span>
|
<span>{{ selected ? selected.manualName : '选择文件预览' }}</span>
|
||||||
@@ -51,17 +51,42 @@
|
|||||||
<el-button size="small" icon="el-icon-edit" @click="handleEdit(selected)">编辑</el-button>
|
<el-button size="small" icon="el-icon-edit" @click="handleEdit(selected)">编辑</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Info card -->
|
<!-- Collapse: info + review -->
|
||||||
<div v-if="selected" class="detail-card">
|
<el-collapse v-if="selected" v-model="collapseActive" class="info-collapse">
|
||||||
<el-row :gutter="16" class="info-row">
|
<el-collapse-item title="文件信息" name="detail">
|
||||||
<el-col :span="8"><span class="info-label">类型</span><span class="info-value"><el-tag :type="tagType(selected.docType)" size="mini">{{ selected.docType }}</el-tag></span></el-col>
|
<div class="detail-grid">
|
||||||
<el-col :span="8"><span class="info-label">版本</span><span class="info-value">{{ selected.version || '-' }}</span></el-col>
|
<div class="detail-item"><span class="detail-label">类型</span><span class="detail-value"><el-tag :type="tagType(selected.docType)" size="mini">{{ selected.docType }}</el-tag></span></div>
|
||||||
<el-col :span="8"><span class="info-label">上传日期</span><span class="info-value">{{ selected.uploadDate || '-' }}</span></el-col>
|
<div class="detail-item"><span class="detail-label">版本</span><span class="detail-value">{{ selected.version || '-' }}</span></div>
|
||||||
</el-row>
|
<div class="detail-item"><span class="detail-label">上传日期</span><span class="detail-value">{{ selected.uploadDate || '-' }}</span></div>
|
||||||
<el-row :gutter="16" class="info-row" v-if="selected.description">
|
<div class="detail-item" v-if="selected.description"><span class="detail-label">描述</span><span class="detail-value">{{ selected.description }}</span></div>
|
||||||
<el-col :span="24"><span class="info-label">描述</span><span class="info-value">{{ selected.description }}</span></el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
<el-collapse-item name="review">
|
||||||
|
<template slot="title">
|
||||||
|
<span>审核意见 <el-tag v-if="fileReviews.length > 0" size="mini" style="margin-left:4px">{{ fileReviews.length }}</el-tag></span>
|
||||||
|
</template>
|
||||||
|
<el-button size="small" type="primary" icon="el-icon-edit" @click="openReviewDialog" style="margin-bottom:8px">发表意见</el-button>
|
||||||
|
<el-timeline v-if="fileReviews.length > 0">
|
||||||
|
<el-timeline-item
|
||||||
|
v-for="item in fileReviews"
|
||||||
|
:key="item.id"
|
||||||
|
:timestamp="item.createTime"
|
||||||
|
placement="top">
|
||||||
|
<div class="review-item">
|
||||||
|
<div class="review-item-header">
|
||||||
|
<strong>{{ item.reviewerName }}</strong>
|
||||||
|
<el-tag v-if="item.reviewAction" size="mini" :type="reviewActionTag(item.reviewAction)" class="review-action-tag">{{ item.reviewAction }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<p class="review-item-content">{{ item.content }}</p>
|
||||||
|
</div>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
<div v-else class="review-empty">
|
||||||
|
<i class="el-icon-chat-dot-round"></i>
|
||||||
|
<span>暂无审核意见</span>
|
||||||
|
</div>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
<!-- Viewer -->
|
<!-- Viewer -->
|
||||||
<div class="viewer-area" ref="viewerContainer">
|
<div class="viewer-area" ref="viewerContainer">
|
||||||
<div v-if="!selected || !selected.fileUrl" class="viewer-placeholder">
|
<div v-if="!selected || !selected.fileUrl" class="viewer-placeholder">
|
||||||
@@ -72,6 +97,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Review Dialog -->
|
||||||
|
<el-dialog title="发表审核意见" :visible.sync="reviewDialogVisible" width="450px" append-to-body>
|
||||||
|
<el-form ref="reviewFormRef" :model="reviewForm" :rules="reviewRules" label-width="80px" size="small">
|
||||||
|
<el-form-item label="审核意见" prop="reviewAction">
|
||||||
|
<el-select v-model="reviewForm.reviewAction" placeholder="请选择" style="width:100%">
|
||||||
|
<el-option label="同意" value="同意" />
|
||||||
|
<el-option label="驳回" value="驳回" />
|
||||||
|
<el-option label="待定" value="待定" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="详细内容" prop="content">
|
||||||
|
<el-input v-model="reviewForm.content" type="textarea" :rows="4" placeholder="请输入审核意见" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button size="small" @click="reviewDialogVisible = false">取消</el-button>
|
||||||
|
<el-button size="small" type="primary" :loading="reviewSubmitting" @click="submitReview">提交</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<!-- Add/Edit Dialog -->
|
<!-- Add/Edit Dialog -->
|
||||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" append-to-body @closed="onClosed">
|
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" append-to-body @closed="onClosed">
|
||||||
<el-form ref="form" :model="form" :rules="rules" label-width="0" size="small">
|
<el-form ref="form" :model="form" :rules="rules" label-width="0" size="small">
|
||||||
@@ -120,6 +165,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { listManualAll, addManual, updateManual, delManual } from '@/api/rm/manual'
|
import { listManualAll, addManual, updateManual, delManual } from '@/api/rm/manual'
|
||||||
import { listProject } from '@/api/rm/project'
|
import { listProject } from '@/api/rm/project'
|
||||||
|
import { listFileReview, addFileReview } from '@/api/rm/fileReview'
|
||||||
|
import { listRmTask } from '@/api/oa/task'
|
||||||
import { getToken } from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
import { mountViewerFrame } from '@flyfish-group/file-viewer-web'
|
import { mountViewerFrame } from '@flyfish-group/file-viewer-web'
|
||||||
|
|
||||||
@@ -140,6 +187,15 @@ export default {
|
|||||||
uploadHeaders: { Authorization: 'Bearer ' + getToken() },
|
uploadHeaders: { Authorization: 'Bearer ' + getToken() },
|
||||||
selected: null,
|
selected: null,
|
||||||
viewerCtrl: null,
|
viewerCtrl: null,
|
||||||
|
collapseActive: [],
|
||||||
|
fileReviews: [],
|
||||||
|
rmTasks: [],
|
||||||
|
reviewDialogVisible: false,
|
||||||
|
reviewForm: { reviewAction: '', content: '' },
|
||||||
|
reviewRules: {
|
||||||
|
reviewAction: [{ required: true, message: '请选择审核意见', trigger: 'change' }]
|
||||||
|
},
|
||||||
|
reviewSubmitting: false,
|
||||||
filterName: '',
|
filterName: '',
|
||||||
filterType: ''
|
filterType: ''
|
||||||
}
|
}
|
||||||
@@ -187,6 +243,7 @@ export default {
|
|||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.mountPreview(row)
|
this.mountPreview(row)
|
||||||
})
|
})
|
||||||
|
this.loadReviews('manual', row.manualId)
|
||||||
},
|
},
|
||||||
mountPreview(row) {
|
mountPreview(row) {
|
||||||
this.destroyViewer()
|
this.destroyViewer()
|
||||||
@@ -226,6 +283,7 @@ export default {
|
|||||||
if (this.selected && this.selected.manualId === row.manualId) {
|
if (this.selected && this.selected.manualId === row.manualId) {
|
||||||
this.destroyViewer()
|
this.destroyViewer()
|
||||||
this.selected = null
|
this.selected = null
|
||||||
|
this.fileReviews = []
|
||||||
}
|
}
|
||||||
this.loadData()
|
this.loadData()
|
||||||
})
|
})
|
||||||
@@ -260,7 +318,37 @@ export default {
|
|||||||
},
|
},
|
||||||
handleUploadError() { this.$message.error('上传失败') },
|
handleUploadError() { this.$message.error('上传失败') },
|
||||||
downloadFile(url) { if (url) window.open(url, '_blank') },
|
downloadFile(url) { if (url) window.open(url, '_blank') },
|
||||||
fileNameFromUrl(url) { if (!url) return ''; return decodeURIComponent(url.split('/').pop() || '') }
|
fileNameFromUrl(url) { if (!url) return ''; return decodeURIComponent(url.split('/').pop() || '') },
|
||||||
|
loadReviews(fileModule, fileId) {
|
||||||
|
if (!fileId) return
|
||||||
|
listFileReview(fileModule, fileId).then(res => {
|
||||||
|
this.fileReviews = res.data || []
|
||||||
|
})
|
||||||
|
},
|
||||||
|
openReviewDialog() {
|
||||||
|
this.reviewForm = { reviewAction: '', content: '' }
|
||||||
|
this.reviewDialogVisible = true
|
||||||
|
this.$nextTick(() => { this.$refs.reviewFormRef?.clearValidate() })
|
||||||
|
},
|
||||||
|
submitReview() {
|
||||||
|
this.$refs.reviewFormRef.validate(valid => {
|
||||||
|
if (!valid) return
|
||||||
|
this.reviewSubmitting = true
|
||||||
|
addFileReview({
|
||||||
|
fileModule: 'manual',
|
||||||
|
fileId: this.selected.manualId,
|
||||||
|
content: this.reviewForm.content,
|
||||||
|
reviewAction: this.reviewForm.reviewAction
|
||||||
|
}).then(() => {
|
||||||
|
this.$message.success('提交成功')
|
||||||
|
this.reviewDialogVisible = false
|
||||||
|
this.loadReviews('manual', this.selected.manualId)
|
||||||
|
}).finally(() => { this.reviewSubmitting = false })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
reviewActionTag(action) {
|
||||||
|
return { '同意': 'success', '驳回': 'danger', '待定': 'warning' }[action] || 'info'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -286,11 +374,21 @@ export default {
|
|||||||
.right-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
|
.right-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; }
|
||||||
.right-actions { display: flex; gap: 4px; }
|
.right-actions { display: flex; gap: 4px; }
|
||||||
|
|
||||||
/* Detail card */
|
/* Collapse info + review */
|
||||||
.detail-card { padding: 10px 12px; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; }
|
.info-collapse { flex-shrink: 0; }
|
||||||
.info-row { margin-bottom: 6px; }
|
.info-collapse >>> .el-collapse-item__header { padding: 0 12px; font-size: 13px; font-weight: 600; height: 36px; }
|
||||||
.info-label { font-size: 11px; color: #909399; margin-right: 6px; }
|
.info-collapse >>> .el-collapse-item__wrap { border-bottom: 1px solid #e8eaed; }
|
||||||
.info-value { font-size: 12px; color: #333; }
|
.info-collapse >>> .el-collapse-item__content { padding: 8px 12px 10px; }
|
||||||
|
.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 16px; }
|
||||||
|
.detail-item { display: flex; font-size: 12px; line-height: 1.8; }
|
||||||
|
.detail-label { color: #909399; width: 65px; flex-shrink: 0; }
|
||||||
|
.detail-value { color: #333; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.review-item-header { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; }
|
||||||
|
.review-item-header strong { font-size: 13px; }
|
||||||
|
.review-item-content { font-size: 12px; color: #555; margin: 2px 0 0; line-height: 1.5; white-space: pre-wrap; }
|
||||||
|
.review-empty { text-align: center; color: #c0c4cc; font-size: 13px; padding: 8px 0; display: flex; flex-direction: column; align-items: center; gap: 4px; }
|
||||||
|
.review-action-tag { font-size: 11px; }
|
||||||
|
.info-collapse >>> .el-timeline-item__timestamp { font-size: 12px; color: #999; }
|
||||||
|
|
||||||
/* Viewer */
|
/* Viewer */
|
||||||
.viewer-area { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; }
|
.viewer-area { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; }
|
||||||
|
|||||||
Reference in New Issue
Block a user