add:1.新增报功页面。2.新增后端报功功能(查询当前参与项目和任务详情,新增报功)。
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package org.nl.pms.projectProgressReport.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.nl.pms.project.vo.ApiResponse;
|
||||
import org.nl.pms.projectProgressReport.dto.ProjectProgressReportCreateDTO;
|
||||
import org.nl.pms.projectProgressReport.service.ProjectProgressReportService;
|
||||
import org.nl.pms.projectProgressReport.vo.ProjectProgressReportDetailVO;
|
||||
import org.nl.pms.projectProgressReport.vo.ProjectProgressTaskListVO;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author liejiu
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/project-progress-report")
|
||||
@RequiredArgsConstructor
|
||||
@CrossOrigin
|
||||
public class ProjectProgressReportController {
|
||||
|
||||
private final ProjectProgressReportService projectProgressReportService;
|
||||
|
||||
@GetMapping("/my-tasks")
|
||||
public ApiResponse<ProjectProgressTaskListVO> listMyProjectTasks() {
|
||||
return ApiResponse.success(projectProgressReportService.listMyProjectTasks());
|
||||
}
|
||||
|
||||
@GetMapping("/detail/{detailId}")
|
||||
public ApiResponse<ProjectProgressReportDetailVO> getDetailReports(@PathVariable Long detailId) {
|
||||
return ApiResponse.success(projectProgressReportService.getDetailReports(detailId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Void> create(@Valid @RequestBody ProjectProgressReportCreateDTO createDTO) {
|
||||
projectProgressReportService.create(createDTO);
|
||||
return ApiResponse.success("提交成功", null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.nl.pms.projectProgressReport.service;
|
||||
|
||||
import org.nl.pms.projectProgressReport.dto.ProjectProgressReportCreateDTO;
|
||||
import org.nl.pms.projectProgressReport.vo.ProjectProgressReportDetailVO;
|
||||
import org.nl.pms.projectProgressReport.vo.ProjectProgressTaskListVO;
|
||||
|
||||
/**
|
||||
* @author liejiu
|
||||
*/
|
||||
public interface ProjectProgressReportService {
|
||||
|
||||
ProjectProgressTaskListVO listMyProjectTasks();
|
||||
|
||||
ProjectProgressReportDetailVO getDetailReports(Long detailId);
|
||||
|
||||
void create(ProjectProgressReportCreateDTO createDTO);
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
package org.nl.pms.projectProgressReport.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.nl.pms.common.exception.BadRequestException;
|
||||
import org.nl.pms.common.utils.SecurityUtils;
|
||||
import org.nl.pms.project.entity.Project;
|
||||
import org.nl.pms.project.mapper.ProjectMapper;
|
||||
import org.nl.pms.projectPlan.entity.ProjectPlanDetail;
|
||||
import org.nl.pms.projectPlan.entity.ProjectPlanMaster;
|
||||
import org.nl.pms.projectPlan.mapper.ProjectPlanDetailMapper;
|
||||
import org.nl.pms.projectPlan.mapper.ProjectPlanMasterMapper;
|
||||
import org.nl.pms.projectProgressReport.dto.ProjectProgressReportCreateDTO;
|
||||
import org.nl.pms.projectProgressReport.entity.ProjectProgressReport;
|
||||
import org.nl.pms.projectProgressReport.mapper.ProjectProgressReportMapper;
|
||||
import org.nl.pms.projectProgressReport.service.ProjectProgressReportService;
|
||||
import org.nl.pms.projectProgressReport.vo.ProjectProgressReportDetailVO;
|
||||
import org.nl.pms.projectProgressReport.vo.ProjectProgressReportVO;
|
||||
import org.nl.pms.projectProgressReport.vo.ProjectProgressTaskListVO;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author liejiu
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ProjectProgressReportServiceImpl implements ProjectProgressReportService {
|
||||
private final ProjectMapper projectMapper;
|
||||
private final ProjectPlanMasterMapper masterMapper;
|
||||
private final ProjectPlanDetailMapper detailMapper;
|
||||
private final ProjectProgressReportMapper reportMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public ProjectProgressTaskListVO listMyProjectTasks() {
|
||||
String user = userName();
|
||||
List<ProjectPlanDetail> mine = detailMapper.selectList(new LambdaQueryWrapper<ProjectPlanDetail>()).stream()
|
||||
.filter(i -> members(i.getTaskMembers()).contains(user)).toList();
|
||||
ProjectProgressTaskListVO vo = new ProjectProgressTaskListVO();
|
||||
if (mine.isEmpty()) {
|
||||
return vo;
|
||||
}
|
||||
Map<Long, ProjectProgressTaskListVO.ProjectTaskVO> map = new LinkedHashMap<>();
|
||||
Map<Long, List<ProjectProgressReport>> reportMap = reportMap(mine.stream().map(ProjectPlanDetail::getId).toList());
|
||||
for (ProjectPlanDetail d : mine) {
|
||||
Project p = projectMapper.selectById(d.getProjectId());
|
||||
ProjectPlanMaster m = masterMapper.selectById(d.getMasterId());
|
||||
if (p == null || m == null) {
|
||||
continue;
|
||||
}
|
||||
ProjectProgressTaskListVO.ProjectTaskVO pvo = map.computeIfAbsent(p.getId(), k -> projectVO(p));
|
||||
pvo.getDetails().add(detailVO(d, m, reportMap.getOrDefault(d.getId(), Collections.emptyList())));
|
||||
pvo.setDetailCount(pvo.getDetails().size());
|
||||
}
|
||||
vo.setProjects(new ArrayList<>(map.values()));
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectProgressReportDetailVO getDetailReports(Long detailId) {
|
||||
ProjectPlanDetail d = detail(detailId);
|
||||
checkMember(d);
|
||||
Project p = projectMapper.selectById(d.getProjectId());
|
||||
ProjectPlanMaster m = masterMapper.selectById(d.getMasterId());
|
||||
if (p == null || m == null) {
|
||||
throw new BadRequestException("任务关联项目数据不存在");
|
||||
}
|
||||
List<ProjectProgressReport> rs = reports(detailId);
|
||||
ProjectProgressReportDetailVO vo = new ProjectProgressReportDetailVO();
|
||||
vo.setDetailId(d.getId());
|
||||
vo.setMasterId(d.getMasterId());
|
||||
vo.setProjectId(d.getProjectId());
|
||||
vo.setProjectCode(p.getProjectCode());
|
||||
vo.setProjectName(p.getProjectName());
|
||||
vo.setMasterName(m.getMasterName());
|
||||
vo.setSubItemName(m.getSubItemName());
|
||||
vo.setDetailSeq(d.getDetailSeq());
|
||||
vo.setDetailName(d.getDetailName());
|
||||
vo.setDetailDescription(d.getDetailDescription());
|
||||
vo.setDevDays(d.getDevDays());
|
||||
vo.setDevStartDate(d.getDevStartDate());
|
||||
vo.setDevEndDate(d.getDevEndDate());
|
||||
vo.setTaskMembers(members(d.getTaskMembers()));
|
||||
vo.setStatus(d.getStatus());
|
||||
vo.setStatusLabel(taskStatus(d.getStatus()));
|
||||
vo.setPriority(d.getPriority());
|
||||
vo.setPriorityLabel(priority(d.getPriority()));
|
||||
vo.setProgress(d.getProgress());
|
||||
vo.setProgressId(d.getProgressId());
|
||||
vo.setRemark(d.getRemark());
|
||||
vo.setCanCreateReport(canCreate(rs));
|
||||
vo.setCanCreateReportReason(reason(rs));
|
||||
vo.setReports(rs.stream().map(this::reportVO).toList());
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void create(ProjectProgressReportCreateDTO dto) {
|
||||
ProjectPlanDetail d = detail(dto.getDetailId());
|
||||
checkMember(d);
|
||||
validate(dto, d);
|
||||
List<ProjectProgressReport> rs = reports(dto.getDetailId());
|
||||
if (!canCreate(rs)) {
|
||||
throw new BadRequestException(reason(rs));
|
||||
}
|
||||
int current = d.getProgress() == null ? 0 : d.getProgress();
|
||||
if (current != dto.getProgressBefore()) {
|
||||
throw new BadRequestException("报功前进度与当前任务进度不一致,请刷新后重试");
|
||||
}
|
||||
ProjectProgressReport r = new ProjectProgressReport();
|
||||
r.setDetailId(d.getId());
|
||||
r.setProjectId(d.getProjectId());
|
||||
r.setReportDate(dto.getReportDate());
|
||||
r.setWorkHours(dto.getWorkHours().setScale(1, RoundingMode.HALF_UP));
|
||||
r.setProgressBefore(dto.getProgressBefore());
|
||||
r.setProgressIncrement(dto.getProgressIncrement());
|
||||
r.setWorkContent(dto.getWorkContent());
|
||||
r.setRemainingWork(dto.getRemainingWork());
|
||||
r.setIsOvertime(dto.getIsOvertime());
|
||||
r.setStatus(1);
|
||||
r.setCreatedName(userName());
|
||||
r.setCreatedTime(LocalDateTime.now());
|
||||
r.setUpdatedTime(LocalDateTime.now());
|
||||
reportMapper.insert(r);
|
||||
int after = dto.getProgressBefore() + dto.getProgressIncrement();
|
||||
d.setProgress(after);
|
||||
if (after >= 100) {
|
||||
d.setStatus(3);
|
||||
} else if (after > 0 && (d.getStatus() == null || d.getStatus() == 1)) {
|
||||
d.setStatus(2);
|
||||
}
|
||||
detailMapper.updateById(d);
|
||||
}
|
||||
|
||||
private ProjectPlanDetail detail(Long id) {
|
||||
ProjectPlanDetail d = detailMapper.selectById(id);
|
||||
if (d == null) {
|
||||
throw new BadRequestException("任务明细不存在");
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
private List<ProjectProgressReport> reports(Long detailId) {
|
||||
return reportMapper.selectList(new LambdaQueryWrapper<ProjectProgressReport>()
|
||||
.eq(ProjectProgressReport::getDetailId, detailId)
|
||||
.orderByDesc(ProjectProgressReport::getCreatedTime)
|
||||
.orderByDesc(ProjectProgressReport::getId));
|
||||
}
|
||||
|
||||
private void checkMember(ProjectPlanDetail d) {
|
||||
if (!members(d.getTaskMembers()).contains(userName())) {
|
||||
throw new BadRequestException("您未参与该任务,无法操作报功数据");
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(ProjectProgressReportCreateDTO dto, ProjectPlanDetail d) {
|
||||
if (dto.getWorkHours().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new BadRequestException("报功工时必须大于0");
|
||||
}
|
||||
if (dto.getWorkHours().compareTo(new BigDecimal("24")) > 0) {
|
||||
throw new BadRequestException("报功工时不能大于24小时");
|
||||
}
|
||||
if (dto.getWorkHours().multiply(new BigDecimal("10")).stripTrailingZeros().scale() > 0) {
|
||||
throw new BadRequestException("报功工时仅支持0.1小时精度");
|
||||
}
|
||||
if (dto.getIsOvertime() == null || (dto.getIsOvertime() != 0 && dto.getIsOvertime() != 1)) {
|
||||
throw new BadRequestException("是否加班取值错误");
|
||||
}
|
||||
if (dto.getProgressBefore() + dto.getProgressIncrement() > 100) {
|
||||
throw new BadRequestException("进度累计不能超过100");
|
||||
}
|
||||
if (d.getStatus() != null && d.getStatus() == 3) {
|
||||
throw new BadRequestException("当前任务已完成,不能继续报功");
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, List<ProjectProgressReport>> reportMap(List<Long> detailIds) {
|
||||
if (detailIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<Long, List<ProjectProgressReport>> map = new LinkedHashMap<>();
|
||||
for (ProjectProgressReport i : reportMapper.selectList(new LambdaQueryWrapper<ProjectProgressReport>().in(ProjectProgressReport::getDetailId, detailIds).orderByDesc(ProjectProgressReport::getCreatedTime).orderByDesc(ProjectProgressReport::getId))) {
|
||||
map.computeIfAbsent(i.getDetailId(), k -> new ArrayList<>()).add(i);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private ProjectProgressTaskListVO.ProjectTaskVO projectVO(Project p) {
|
||||
ProjectProgressTaskListVO.ProjectTaskVO vo = new ProjectProgressTaskListVO.ProjectTaskVO();
|
||||
vo.setProjectId(p.getId());
|
||||
vo.setProjectCode(p.getProjectCode());
|
||||
vo.setProjectName(p.getProjectName());
|
||||
vo.setProjectType(p.getProjectType());
|
||||
vo.setDevelopLeader(p.getDevelopLeader());
|
||||
vo.setProjectLeader(p.getProjectLeader());
|
||||
vo.setDetailCount(0); return vo;
|
||||
}
|
||||
|
||||
private ProjectProgressTaskListVO.TaskDetailVO detailVO(ProjectPlanDetail d, ProjectPlanMaster m, List<ProjectProgressReport> rs) {
|
||||
ProjectProgressTaskListVO.TaskDetailVO vo = new ProjectProgressTaskListVO.TaskDetailVO();
|
||||
vo.setDetailId(d.getId()); vo.setMasterId(d.getMasterId());
|
||||
vo.setMasterName(m.getMasterName());
|
||||
vo.setSubItemName(m.getSubItemName());
|
||||
vo.setDetailSeq(d.getDetailSeq());
|
||||
vo.setDetailName(d.getDetailName());
|
||||
vo.setDetailDescription(d.getDetailDescription());
|
||||
vo.setDevDays(d.getDevDays());
|
||||
vo.setDevStartDate(d.getDevStartDate());
|
||||
vo.setDevEndDate(d.getDevEndDate());
|
||||
vo.setTaskMembers(members(d.getTaskMembers()));
|
||||
vo.setStatus(d.getStatus());
|
||||
vo.setStatusLabel(taskStatus(d.getStatus()));
|
||||
vo.setPriority(d.getPriority());
|
||||
vo.setPriorityLabel(priority(d.getPriority()));
|
||||
vo.setProgress(d.getProgress());
|
||||
vo.setProgressId(d.getProgressId());
|
||||
vo.setRemark(d.getRemark());
|
||||
vo.setCanCreateReport(canCreate(rs));
|
||||
vo.setCanCreateReportReason(reason(rs));
|
||||
vo.setReportCount(rs.size());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private ProjectProgressReportVO reportVO(ProjectProgressReport r) {
|
||||
ProjectProgressReportVO vo = new ProjectProgressReportVO();
|
||||
vo.setId(r.getId()); vo.setDetailId(r.getDetailId());
|
||||
vo.setProjectId(r.getProjectId());
|
||||
vo.setReportDate(r.getReportDate());
|
||||
vo.setWorkHours(r.getWorkHours());
|
||||
vo.setProgressBefore(r.getProgressBefore());
|
||||
vo.setProgressIncrement(r.getProgressIncrement());
|
||||
vo.setProgressAfter((r.getProgressBefore() == null ? 0 : r.getProgressBefore()) + (r.getProgressIncrement() == null ? 0 : r.getProgressIncrement()));
|
||||
vo.setWorkContent(r.getWorkContent()); vo.setRemainingWork(r.getRemainingWork());
|
||||
vo.setIsOvertime(r.getIsOvertime());
|
||||
vo.setIsOvertimeLabel(r.getIsOvertime() != null && r.getIsOvertime() == 1 ? "是" : "否");
|
||||
vo.setStatus(r.getStatus());
|
||||
vo.setStatusLabel(reportStatus(r.getStatus()));
|
||||
vo.setCreatedName(r.getCreatedName());
|
||||
vo.setCreatedTime(r.getCreatedTime());
|
||||
vo.setUpdatedTime(r.getUpdatedTime());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private boolean canCreate(List<ProjectProgressReport> rs) {
|
||||
return rs == null || rs.isEmpty() || (rs.get(0).getStatus() != null && rs.get(0).getStatus() == 2);
|
||||
}
|
||||
|
||||
private String reason(List<ProjectProgressReport> rs) {
|
||||
return canCreate(rs) ? "可以新增报功" : "上一条报功审核未结束,暂不能发起当前报功";
|
||||
}
|
||||
|
||||
private List<String> members(String json) {
|
||||
if (!StringUtils.hasText(json)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(json, new TypeReference<List<String>>() {});
|
||||
} catch (Exception e) {
|
||||
throw new BadRequestException("任务成员数据解析失败");
|
||||
}
|
||||
}
|
||||
|
||||
private String userName() {
|
||||
String n = SecurityUtils.getCurrentNickName();
|
||||
if (!StringUtils.hasText(n)) {
|
||||
n = SecurityUtils.getCurrentUsername();
|
||||
}
|
||||
if (!StringUtils.hasText(n)) {
|
||||
throw new BadRequestException("未获取到当前登录用户信息");
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private String taskStatus(Integer s) {
|
||||
if (ObjectUtil.isEmpty(s)) {
|
||||
return "未知";
|
||||
}
|
||||
return switch (s) {
|
||||
case 1 -> "待开始";
|
||||
case 2 -> "进行中";
|
||||
case 3 -> "已完成";
|
||||
case 4 -> "阻塞/延期";
|
||||
default -> "未知";
|
||||
};
|
||||
}
|
||||
|
||||
private String priority(Integer p) {
|
||||
if (ObjectUtil.isEmpty(p)) {
|
||||
return "普通";
|
||||
}
|
||||
return switch (p) {
|
||||
case 1 -> "紧急";
|
||||
case 2 -> "普通";
|
||||
case 3 -> "低";
|
||||
default -> "普通";
|
||||
};
|
||||
}
|
||||
|
||||
private String reportStatus(Integer s) {
|
||||
if (ObjectUtil.isEmpty(s)) {
|
||||
return "未知";
|
||||
}
|
||||
return switch (s) {
|
||||
case 1 -> "待审核";
|
||||
case 2 -> "已确认";
|
||||
default -> "未知";
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -139,6 +139,17 @@ const staticMenuData = [
|
||||
icon: 'clipboard',
|
||||
noCache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'ProgressReport',
|
||||
path: 'progress-report',
|
||||
hidden: false,
|
||||
component: 'project/progressReportIndex',
|
||||
meta: {
|
||||
title: '报功',
|
||||
icon: 'clipboard',
|
||||
noCache: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
25
pms_vue/src/views/project/progressReportApi.js
Normal file
25
pms_vue/src/views/project/progressReportApi.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getMyTasks() {
|
||||
return request({
|
||||
url: 'api/project-progress-report/my-tasks',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getReportsByDetail(detailId) {
|
||||
return request({
|
||||
url: `api/project-progress-report/detail/${detailId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addReport(data) {
|
||||
return request({
|
||||
url: 'api/project-progress-report',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export default { getMyTasks, getReportsByDetail, addReport }
|
||||
242
pms_vue/src/views/project/progressReportIndex.vue
Normal file
242
pms_vue/src/views/project/progressReportIndex.vue
Normal file
@@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<div class="app-container page">
|
||||
<div class="left">
|
||||
<el-card shadow="never" v-loading="loading">
|
||||
<div slot="header" class="hd"><span>我参与的项目</span></div>
|
||||
<div v-if="!projects.length && !loading" class="empty">暂无进行中的项目</div>
|
||||
<div v-for="p in projects" :key="p.projectId" class="item" :class="{on:cp&&cp.projectId===p.projectId}" @click="pickProject(p)">
|
||||
<div class="t">{{ p.projectName }}</div>
|
||||
<div class="s">{{ p.projectCode }}</div>
|
||||
<div class="s">开发负责人:{{ p.developLeader || '-' }}</div>
|
||||
<div class="s">任务明细:{{ p.detailCount || 0 }}项</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card v-if="cp" shadow="never" class="mt16 tasks">
|
||||
<div slot="header" class="hd"><span>工作明细</span><span class="sub">{{ tasks.length }}项</span></div>
|
||||
<div v-if="!tasks.length" class="empty">该项目下暂无分配给您的任务</div>
|
||||
<div v-for="t in tasks" :key="t.detailId" class="item" :class="{on:ct&&ct.detailId===t.detailId}" @click="pickTask(t)">
|
||||
<div class="row"><el-tag size="mini" :type="tTag(t.status)">{{ t.statusLabel || tLabel(t.status) }}</el-tag><el-tag size="mini" :type="priTag(t.priority)" style="margin-left:4px">{{ t.priorityLabel || priLabel(t.priority) }}</el-tag><span class="t" style="margin-left:6px">{{ t.detailName }}</span></div>
|
||||
<div class="s">主项:{{ t.masterName || '-' }} / 分项:{{ t.subItemName || '-' }}</div>
|
||||
<div class="s">{{ t.devStartDate || '-' }} ~ {{ t.devEndDate || '-' }}</div>
|
||||
<div class="s">进度 {{ t.progress || 0 }}%</div>
|
||||
<el-progress :percentage="t.progress || 0" :show-text="false" :stroke-width="4" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div class="right">
|
||||
<el-card v-if="!ct" shadow="never" class="ph"><div class="empty big">请先选择工作明细</div></el-card>
|
||||
<template v-else>
|
||||
<el-card shadow="never">
|
||||
<div class="top">
|
||||
<div>
|
||||
<div class="row"><span class="t">当前任务:{{ ct.detailName }}</span><el-tag size="mini" :type="tTag(ct.status)" style="margin-left:8px">{{ ct.statusLabel || tLabel(ct.status) }}</el-tag><el-tag size="mini" :type="priTag(ct.priority)" style="margin-left:4px">{{ ct.priorityLabel || priLabel(ct.priority) }}</el-tag></div>
|
||||
<div class="s">主项:{{ ct.masterName || '-' }} / 分项:{{ ct.subItemName || '-' }}</div>
|
||||
<div class="s">开发周期:{{ ct.devStartDate || '-' }} ~ {{ ct.devEndDate || '-' }}({{ ct.devDays || 0 }}天)</div>
|
||||
<div class="s">成员:{{ members(ct.taskMembers) }}</div>
|
||||
</div>
|
||||
<el-button type="primary" size="small" icon="el-icon-plus" :disabled="!canAdd" @click="open">新增报功</el-button>
|
||||
</div>
|
||||
<div v-if="!canAdd" class="warn">{{ ct.canCreateReportReason || '当前不能新增报功' }}</div>
|
||||
<el-progress :percentage="ct.progress || 0" :stroke-width="10" />
|
||||
</el-card>
|
||||
<el-card shadow="never" class="mt16">
|
||||
<div slot="header" class="hd"><span>报功记录</span><span class="sub">{{ reports.length }}条</span></div>
|
||||
<div v-loading="loadingReports">
|
||||
<div v-if="!reports.length && !loadingReports" class="empty big">暂无报功记录</div>
|
||||
<div v-for="(r,i) in reports" :key="r.id" class="rp">
|
||||
<div class="rph">
|
||||
<div class="row"><span class="t">第 {{ reports.length-i }} 次报功</span><span class="s" style="margin-left:10px">{{ r.reportDate }}</span><el-tag size="mini" :type="r.status===2?'success':'warning'" style="margin-left:8px">{{ r.status===2?'已确认':'待确认' }}</el-tag></div>
|
||||
</div>
|
||||
<el-row :gutter="12" class="rpb">
|
||||
<el-col :span="6"><div class="f"><span class="k">报工工时</span><span>{{ r.workHours }} h</span></div></el-col>
|
||||
<el-col :span="6"><div class="f"><span class="k">报工前进度</span><span>{{ r.progressBefore }}%</span></div></el-col>
|
||||
<el-col :span="6"><div class="f"><span class="k">本次进度增量</span><span class="ok">+{{ r.progressIncrement }}%</span></div></el-col>
|
||||
<el-col :span="6"><div class="f"><span class="k">是否加班</span><span>{{ r.isOvertime?'是':'否' }}</span></div></el-col>
|
||||
<el-col :span="12"><div class="f"><span class="k">本次工作内容</span><span>{{ r.workContent || '-' }}</span></div></el-col>
|
||||
<el-col :span="12"><div class="f"><span class="k">剩余工作计划</span><span>{{ r.remainingWork || '-' }}</span></div></el-col>
|
||||
<el-col :span="12"><div class="f"><span class="k">提交人</span><span>{{ r.createdName || '-' }}</span></div></el-col>
|
||||
<el-col :span="12"><div class="f"><span class="k">提交时间</span><span>{{ r.createdTime || '-' }}</span></div></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</div>
|
||||
<el-dialog title="新增报功" :visible.sync="show" width="580px" :close-on-click-modal="false" @close="reset">
|
||||
<el-form ref="f" :model="form" :rules="rules" label-width="110px" size="small">
|
||||
<el-form-item label="报工日期" prop="reportDate"><el-date-picker v-model="form.reportDate" type="date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="报工工时(h)" prop="workHours"><el-input-number v-model="form.workHours" :min="0.5" :max="24" :step="0.5" :precision="1" controls-position="right" style="width:100%" /></el-form-item>
|
||||
<el-form-item label="报工前进度" prop="progressBefore"><el-input-number v-model="form.progressBefore" :min="0" :max="100" disabled controls-position="right" style="width:100%" /><div class="tip">自动取当前任务进度</div></el-form-item>
|
||||
<el-form-item label="进度增量(%)" prop="progressIncrement"><el-input-number v-model="form.progressIncrement" :min="0" :max="100-form.progressBefore" :step="5" controls-position="right" style="width:100%" /><div class="tip">本次完成后预计总进度:{{ form.progressBefore+form.progressIncrement }}%</div></el-form-item>
|
||||
<el-form-item label="是否加班"><el-radio-group v-model="form.isOvertime"><el-radio :label="0">否</el-radio><el-radio :label="1">是</el-radio></el-radio-group></el-form-item>
|
||||
<el-form-item label="本次工作内容" prop="workContent"><el-input v-model="form.workContent" type="textarea" :rows="3" maxlength="500" show-word-limit /></el-form-item>
|
||||
<el-form-item label="剩余工作计划"><el-input v-model="form.remainingWork" type="textarea" :rows="3" maxlength="500" show-word-limit /></el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer"><el-button size="small" @click="show=false">取消</el-button><el-button type="primary" size="small" :loading="sub" @click="submit">提交报功</el-button></div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { getMyTasks, getReportsByDetail, addReport } from '@/views/project/progressReportApi'
|
||||
|
||||
const df = { reportDate: '', workHours: 8, progressBefore: 0, progressIncrement: 0, isOvertime: 0, workContent: '', remainingWork: '' }
|
||||
|
||||
export default {
|
||||
name: 'ProgressReportIndex',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
loadingReports: false,
|
||||
sub: false,
|
||||
show: false,
|
||||
projects: [],
|
||||
tasks: [],
|
||||
reports: [],
|
||||
cp: null,
|
||||
ct: null,
|
||||
form: { ...df },
|
||||
rules: {
|
||||
reportDate: [{ required: true, message: '请选择报工日期', trigger: 'change' }],
|
||||
workHours: [{ required: true, message: '请输入报工工时', trigger: 'change' }],
|
||||
progressIncrement: [{ required: true, message: '请输入本次进度增量', trigger: 'change' }],
|
||||
workContent: [{ required: true, message: '请填写本次工作内容', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
latest() { return this.reports.length ? this.reports[0] : null },
|
||||
canAdd() {
|
||||
if (!this.ct) return false
|
||||
return this.ct.canCreateReport === true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadMyTasks()
|
||||
},
|
||||
methods: {
|
||||
loadMyTasks() {
|
||||
this.loading = true
|
||||
getMyTasks().then(res => {
|
||||
const projects = (res.data && res.data.projects) ? res.data.projects : []
|
||||
this.projects = projects
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
pickProject(p) {
|
||||
if (this.cp && this.cp.projectId === p.projectId) return
|
||||
this.cp = p
|
||||
this.ct = null
|
||||
this.reports = []
|
||||
this.tasks = p.details || []
|
||||
},
|
||||
pickTask(t) {
|
||||
this.ct = t
|
||||
this.loadReports(t.detailId)
|
||||
},
|
||||
loadReports(detailId) {
|
||||
this.loadingReports = true
|
||||
getReportsByDetail(detailId).then(res => {
|
||||
const detail = res.data || {}
|
||||
const list = Array.isArray(detail.reports) ? detail.reports : []
|
||||
this.reports = list.sort((a, b) => new Date(b.createdTime) - new Date(a.createdTime))
|
||||
if (this.ct && this.ct.detailId === detailId) {
|
||||
this.ct = { ...this.ct, ...detail }
|
||||
}
|
||||
const t = this.tasks.find(x => x.detailId === detailId)
|
||||
if (t) {
|
||||
Object.assign(t, detail)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loadingReports = false
|
||||
})
|
||||
},
|
||||
open() {
|
||||
if (!this.canAdd) {
|
||||
this.$message.warning(this.ct ? this.ct.canCreateReportReason : '暂无法新增报功')
|
||||
return
|
||||
}
|
||||
this.form = { ...df, reportDate: this.today(), progressBefore: this.ct ? (this.ct.progress || 0) : 0 }
|
||||
this.show = true
|
||||
this.$nextTick(() => { this.$refs.f && this.$refs.f.clearValidate() })
|
||||
},
|
||||
reset() {
|
||||
this.form = { ...df }
|
||||
this.$refs.f && this.$refs.f.clearValidate()
|
||||
},
|
||||
submit() {
|
||||
this.$refs.f.validate(v => {
|
||||
if (!v) return
|
||||
const max = 100 - this.form.progressBefore
|
||||
if (this.form.progressIncrement > max) {
|
||||
this.$message.warning('进度增量不能超过' + max + '%')
|
||||
return
|
||||
}
|
||||
this.sub = true
|
||||
addReport({
|
||||
detailId: this.ct.detailId,
|
||||
projectId: this.cp.projectId,
|
||||
reportDate: this.form.reportDate,
|
||||
workHours: this.form.workHours,
|
||||
progressBefore: this.form.progressBefore,
|
||||
progressIncrement: this.form.progressIncrement,
|
||||
workContent: this.form.workContent,
|
||||
remainingWork: this.form.remainingWork,
|
||||
isOvertime: this.form.isOvertime
|
||||
}).then(() => {
|
||||
this.$message.success('提交成功')
|
||||
const newProgress = this.form.progressBefore + this.form.progressIncrement
|
||||
this.ct.progress = newProgress
|
||||
const t = this.tasks.find(x => x.detailId === this.ct.detailId)
|
||||
if (t) {
|
||||
t.progress = newProgress
|
||||
t.canCreateReport = false
|
||||
t.canCreateReportReason = '上次报功尚未审核,不能发起新报功'
|
||||
}
|
||||
this.show = false
|
||||
this.loadReports(this.ct.detailId)
|
||||
}).finally(() => {
|
||||
this.sub = false
|
||||
})
|
||||
})
|
||||
},
|
||||
today() {
|
||||
const d = new Date()
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return y + '-' + m + '-' + day
|
||||
},
|
||||
members(v) { return Array.isArray(v) ? v.join('、') : '-' },
|
||||
tLabel(v) { return ({ 1: '待开始', 2: '进行中', 3: '已完成', 4: '阻塞' })[v] || '未知' },
|
||||
priLabel(v) { return ({ 1: '紧急', 2: '普通', 3: '低' })[v] || '-' },
|
||||
tTag(v) { return ({ 1: 'info', 2: 'warning', 3: 'success', 4: 'danger' })[v] || 'info' },
|
||||
priTag(v) { return ({ 1: 'danger', 2: 'warning', 3: 'info' })[v] || 'info' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.page{display:flex;gap:16px;align-items:flex-start;background:#f5f7fa;min-height:calc(100vh - 120px)}
|
||||
.left{width:320px;flex-shrink:0}
|
||||
.right{flex:1;min-width:0}
|
||||
.mt16{margin-top:16px}
|
||||
.hd,.top,.row,.rph{display:flex;align-items:center}
|
||||
.hd,.top,.rph{justify-content:space-between}
|
||||
.sub,.s,.k,.tip{font-size:12px;color:#909399}
|
||||
.t{font-weight:600;color:#303133}
|
||||
.item{padding:12px;border:1px solid #ebeef5;border-radius:6px;background:#fff;cursor:pointer;margin-bottom:10px;transition:.2s}
|
||||
.item:hover,.item.on{border-color:#409eff;background:#ecf5ff}
|
||||
.tasks{max-height:62vh;overflow:auto}
|
||||
.ph{min-height:320px}
|
||||
.empty{color:#c0c4cc;text-align:center;padding:24px 0}
|
||||
.big{padding:120px 0}
|
||||
.top{margin-bottom:12px;gap:12px}
|
||||
.warn{font-size:12px;color:#e6a23c;margin-bottom:10px}
|
||||
.rp{border:1px solid #ebeef5;border-radius:6px;overflow:hidden;margin-bottom:14px;background:#fafafa}
|
||||
.rph{padding:12px 16px;background:#f5f7fa;border-bottom:1px solid #ebeef5}
|
||||
.rpb{padding:12px 16px 2px}
|
||||
.f{display:flex;flex-direction:column;gap:4px;margin-bottom:12px;word-break:break-all}
|
||||
.ok{color:#67c23a;font-weight:700}
|
||||
</style>
|
||||
Reference in New Issue
Block a user