设计计划

This commit is contained in:
zt
2025-10-21 15:53:56 +08:00
parent f8eea0f63f
commit 37d0c776c0
12 changed files with 1393 additions and 1 deletions

View File

@ -0,0 +1,146 @@
package org.dromara.design.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.collection.CollUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.design.domain.DesConstructionSchedulePlan;
import org.dromara.design.domain.dto.constructionscheduleplan.*;
import org.dromara.design.domain.vo.DesConstructionSchedulePlanVo;
import org.dromara.design.service.IDesConstructionSchedulePlanService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 设计计划
*
* @author lilemy
* @date 2025-08-01
*/
@Validated
@RestController
@RequestMapping("/design/constructionSchedulePlan")
public class DesConstructionSchedulePlanController extends BaseController {
@Resource
private IDesConstructionSchedulePlanService desConstructionSchedulePlanService;
/**
* 查询设计计划列表
*/
@SaCheckPermission("design:constructionSchedulePlan:list")
@GetMapping("/list")
public R<List<DesConstructionSchedulePlanVo>> list(DesConstructionSchedulePlanQueryReq req) {
List<DesConstructionSchedulePlanVo> list = desConstructionSchedulePlanService.queryList(req);
return R.ok(list);
}
/**
* 导出设计计划列表
*/
@SaCheckPermission("design:constructionSchedulePlan:export")
@Log(title = "施工进度计划", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(DesConstructionSchedulePlanQueryReq req, HttpServletResponse response) {
List<DesConstructionSchedulePlanVo> list = desConstructionSchedulePlanService.queryList(req);
ExcelUtil.exportExcel(list, "施工进度计划", DesConstructionSchedulePlanVo.class, response);
}
/**
* 根据项目id导出设计计划模版
*/
@SaCheckPermission("design:constructionSchedulePlan:exportTemplate")
@Log(title = "施工进度计划", businessType = BusinessType.EXPORT)
@PostMapping("/exportTemplate/{projectId}")
public void exportExcelByProjectId(@NotNull(message = "项目id不能为空")
@PathVariable Long projectId, HttpServletResponse response) {
desConstructionSchedulePlanService.exportExcelByProjectId(projectId, response);
}
/**
* 读取设计计划模版
*/
@SaCheckPermission("design:constructionSchedulePlan:readTemplate")
@Log(title = "施工进度计划", businessType = BusinessType.IMPORT)
@PostMapping("/readTemplate")
public R<Void> readExcel(@RequestParam("file") MultipartFile file, Long projectId) {
List<DesConstructionSchedulePlanExcelDto> list = desConstructionSchedulePlanService.readExcel(file, projectId);
if (CollUtil.isNotEmpty(list)) {
List<DesConstructionSchedulePlan> planList = desConstructionSchedulePlanService.convertToEntities(list);
return toAjax(desConstructionSchedulePlanService.saveBatch(planList));
}
return toAjax(true);
}
/**
* 获取设计计划详细信息
*
* @param id 主键
*/
@SaCheckPermission("design:constructionSchedulePlan:query")
@GetMapping("/{id}")
public R<DesConstructionSchedulePlanVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(desConstructionSchedulePlanService.queryById(id));
}
/**
* 新增设计计划
*/
@SaCheckPermission("design:constructionSchedulePlan:add")
@Log(title = "施工进度计划", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<DesConstructionSchedulePlanVo> add(@Validated @RequestBody DesConstructionSchedulePlanCreateReq req) {
return R.ok(desConstructionSchedulePlanService.insertByBo(req));
}
/**
* 修改设计计划
*/
@SaCheckPermission("design:constructionSchedulePlan:edit")
@Log(title = "施工进度计划", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated @RequestBody DesConstructionSchedulePlanUpdateReq req) {
return toAjax(desConstructionSchedulePlanService.updateByBo(req));
}
/**
* 修改设计计划为完成状态
*
*/
@SaCheckPermission("design:constructionSchedulePlan:editFinish")
@Log(title = "施工进度计划", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/finish")
public R<Void> editFinish(@Validated @RequestBody DesConstructionSchedulePlanFinishReq req) {
return toAjax(desConstructionSchedulePlanService.updateFinish(req));
}
/**
* 删除设计计划
*
* @param ids 主键串
*/
@SaCheckPermission("design:constructionSchedulePlan:remove")
@Log(title = "施工进度计划", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(desConstructionSchedulePlanService.deleteByIds(List.of(ids)));
}
}

View File

@ -0,0 +1,87 @@
package org.dromara.design.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
import java.time.LocalDate;
/**
* 施工进度计划对象 pgs_construction_schedule_plan
*
* @author lilemy
* @date 2025-08-01
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("des_construction_schedule_plan")
public class DesConstructionSchedulePlan extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 项目ID
*/
private Long projectId;
/**
* 父ID
*/
private Long parentId;
/**
* 节点名称
*/
private String nodeName;
/**
* 对应项目结构
*/
private Long projectStructure;
/**
* 对应项目结构名称
*/
private String projectStructureName;
/**
* 预计开始时间
*/
private LocalDate planStartDate;
/**
* 预计结束时间
*/
private LocalDate planEndDate;
/**
* 实际开始时间
*/
private LocalDate practicalStartDate;
/**
* 实际结束时间
*/
private LocalDate practicalEndDate;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,77 @@
package org.dromara.design.domain.dto.constructionscheduleplan;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
/**
* @author lilemy
* @date 2025-08-01 14:05
*/
@Data
public class DesConstructionSchedulePlanCreateReq implements Serializable {
@Serial
private static final long serialVersionUID = 4060838737379600701L;
/**
* 项目ID
*/
@NotNull(message = "项目ID不能为空")
private Long projectId;
/**
* 父ID
*/
private Long parentId;
/**
* 节点名称
*/
@NotBlank(message = "节点名称不能为空")
private String nodeName;
/**
* 对应项目结构
*/
private Long projectStructure;
/**
* 对应项目结构名称
*/
private String projectStructureName;
/**
* 预计开始时间
*/
private LocalDate planStartDate;
/**
* 预计结束时间
*/
private LocalDate planEndDate;
/**
* 实际开始时间
*/
private LocalDate practicalStartDate;
/**
* 实际结束时间
*/
private LocalDate practicalEndDate;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,71 @@
package org.dromara.design.domain.dto.constructionscheduleplan;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.time.LocalDate;
/**
* @author lilemy
* @date 2025-09-06 17:12
*/
@Data
@AllArgsConstructor
public class DesConstructionSchedulePlanExcelDto {
/**
* 编号1, 1.1, 1.1.1
*/
private String number;
/**
* 项目ID
*/
private Long projectId;
/**
* 节点名称
*/
private String nodeName;
/**
* 对应项目结构
*/
private Long projectStructure;
/**
* 对应项目结构名称
*/
private String projectStructureName;
/**
* 预计开始时间
*/
private LocalDate planStartDate;
/**
* 预计结束时间
*/
private LocalDate planEndDate;
/**
* 实际开始时间
*/
private LocalDate practicalStartDate;
/**
* 实际结束时间
*/
private LocalDate practicalEndDate;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,31 @@
package org.dromara.design.domain.dto.constructionscheduleplan;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
/**
* @author lilemy
* @date 2025-09-17 10:15
*/
@Data
public class DesConstructionSchedulePlanFinishReq implements Serializable {
@Serial
private static final long serialVersionUID = 8139653508791280689L;
/**
* 主键
*/
@NotNull(message = "主键不能为空")
private Long id;
/**
* 完成时间
*/
@NotNull(message = "完成时间不能为空")
private LocalDate finishDate;
}

View File

@ -0,0 +1,37 @@
package org.dromara.design.domain.dto.constructionscheduleplan;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author lilemy
* @date 2025-08-01 14:05
*/
@Data
public class DesConstructionSchedulePlanQueryReq implements Serializable {
@Serial
private static final long serialVersionUID = 9021370369055688811L;
/**
* 项目ID
*/
private Long projectId;
/**
* 节点名称
*/
private String nodeName;
/**
* 对应项目结构名称
*/
private String projectStructureName;
/**
* 状态
*/
private String status;
}

View File

@ -0,0 +1,74 @@
package org.dromara.design.domain.dto.constructionscheduleplan;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
/**
* @author lilemy
* @date 2025-08-01 14:06
*/
@Data
public class DesConstructionSchedulePlanUpdateReq implements Serializable {
@Serial
private static final long serialVersionUID = 6955873817030428268L;
/**
* 主键ID
*/
private Long id;
/**
* 节点名称
*/
private String nodeName;
/**
* 父ID
*/
private Long parentId;
/**
* 对应项目结构
*/
private Long projectStructure;
/**
* 对应项目结构名称
*/
private String projectStructureName;
/**
* 预计开始时间
*/
private LocalDate planStartDate;
/**
* 预计结束时间
*/
private LocalDate planEndDate;
/**
* 实际开始时间
*/
private LocalDate practicalStartDate;
/**
* 实际结束时间
*/
private LocalDate practicalEndDate;
/**
* 状态
*/
private String status;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,104 @@
package org.dromara.design.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.progress.domain.PgsConstructionSchedulePlan;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDate;
/**
* 施工进度计划视图对象 pgs_construction_schedule_plan
*
* @author lilemy
* @date 2025-08-01
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = PgsConstructionSchedulePlan.class)
public class DesConstructionSchedulePlanVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 项目ID
*/
@ExcelProperty(value = "项目ID")
private Long projectId;
/**
* 父ID
*/
@ExcelProperty(value = "父ID")
private Long parentId;
/**
* 节点名称
*/
@ExcelProperty(value = "节点名称")
private String nodeName;
/**
* 对应项目结构
*/
@ExcelProperty(value = "对应项目结构")
private Long projectStructure;
/**
* 对应项目结构名称
*/
@ExcelProperty(value = "对应项目结构名称")
private String projectStructureName;
/**
* 预计开始时间
*/
@ExcelProperty(value = "预计开始时间")
private LocalDate planStartDate;
/**
* 预计结束时间
*/
@ExcelProperty(value = "预计结束时间")
private LocalDate planEndDate;
/**
* 实际开始时间
*/
@ExcelProperty(value = "实际开始时间")
private LocalDate practicalStartDate;
/**
* 实际结束时间
*/
@ExcelProperty(value = "实际结束时间")
private LocalDate practicalEndDate;
/**
* 状态
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "project_construction_status")
private String status;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@ -0,0 +1,17 @@
package org.dromara.design.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.design.domain.DesConstructionSchedulePlan;
import org.dromara.design.domain.vo.DesConstructionSchedulePlanVo;
import org.dromara.progress.domain.PgsConstructionSchedulePlan;
import org.dromara.progress.domain.vo.constructionscheduleplan.PgsConstructionSchedulePlanVo;
/**
* 施工进度计划Mapper接口
*
* @author lilemy
* @date 2025-08-01
*/
public interface DesConstructionSchedulePlanMapper extends BaseMapperPlus<DesConstructionSchedulePlan, DesConstructionSchedulePlanVo> {
}

View File

@ -0,0 +1,117 @@
package org.dromara.design.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.design.domain.DesConstructionSchedulePlan;
import org.dromara.design.domain.dto.constructionscheduleplan.*;
import org.dromara.design.domain.vo.DesConstructionSchedulePlanVo;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collection;
import java.util.List;
/**
* 施工进度计划Service接口
*
* @author lilemy
* @date 2025-08-01
*/
public interface IDesConstructionSchedulePlanService extends IService<DesConstructionSchedulePlan> {
/**
* 查询施工进度计划
*
* @param id 主键
* @return 施工进度计划
*/
DesConstructionSchedulePlanVo queryById(Long id);
/**
* 查询符合条件的施工进度计划列表
*
* @param req 查询条件
* @return 施工进度计划列表
*/
List<DesConstructionSchedulePlanVo> queryList(DesConstructionSchedulePlanQueryReq req);
/**
* 新增施工进度计划
*
* @param req 施工进度计划
* @return 新增施工进度计划封装
*/
DesConstructionSchedulePlanVo insertByBo(DesConstructionSchedulePlanCreateReq req);
/**
* 修改施工进度计划
*
* @param req 施工进度计划
* @return 是否修改成功
*/
Boolean updateByBo(DesConstructionSchedulePlanUpdateReq req);
/**
* 修改施工进度计划为完成状态
*
* @param req 施工进度计划
* @return 是否修改成功
*/
Boolean updateFinish(DesConstructionSchedulePlanFinishReq req);
/**
* 批量删除施工进度计划信息
*
* @param ids 待删除的主键集合
* @return 是否删除成功
*/
Boolean deleteByIds(Collection<Long> ids);
/**
* 获取施工进度计划视图对象
*
* @param constructionSchedulePlan 施工进度计划对象
* @return 施工进度计划视图对象
*/
DesConstructionSchedulePlanVo getVo(DesConstructionSchedulePlan constructionSchedulePlan);
/**
* 获取施工进度计划查询条件封装
*
* @param req 查询条件
* @return 查询条件封装
*/
LambdaQueryWrapper<DesConstructionSchedulePlan> buildQueryWrapper(DesConstructionSchedulePlanQueryReq req);
/**
* 获取施工进度计划分页对象视图
*
* @param constructionSchedulePlanList 施工进度计划分页对象
* @return 施工进度计划分页对象视图
*/
List<DesConstructionSchedulePlanVo> getVoList(List<DesConstructionSchedulePlan> constructionSchedulePlanList);
/**
* 导出Excel
*
* @param projectId 项目id
*/
void exportExcelByProjectId(Long projectId, HttpServletResponse response);
/**
* 读取Excel文件
*
* @param file Excel文件
* @param projectId 项目ID
* @return 读取的数据列表
*/
List<DesConstructionSchedulePlanExcelDto> readExcel(MultipartFile file, Long projectId);
/**
* 将Excel数据转换为实体列表
*
* @param excelList Excel数据列表
* @return 实体列表
*/
List<DesConstructionSchedulePlan> convertToEntities(List<DesConstructionSchedulePlanExcelDto> excelList);
}

View File

@ -0,0 +1,627 @@
package org.dromara.design.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.design.domain.DesConstructionSchedulePlan;
import org.dromara.design.domain.bo.DesUserBo;
import org.dromara.design.domain.dto.constructionscheduleplan.*;
import org.dromara.design.domain.vo.DesConstructionSchedulePlanVo;
import org.dromara.design.mapper.DesConstructionSchedulePlanMapper;
import org.dromara.design.service.IDesConstructionSchedulePlanService;
import org.dromara.project.service.IBusProjectService;
import org.dromara.system.domain.vo.SysDictDataVo;
import org.dromara.system.service.ISysDictDataService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
/**
* 施工进度计划Service业务层处理
*
* @author lilemy
* @date 2025-08-01
*/
@Slf4j
@Service
public class DesConstructionSchedulePlanServiceImpl extends ServiceImpl<DesConstructionSchedulePlanMapper, DesConstructionSchedulePlan>
implements IDesConstructionSchedulePlanService {
@Resource
private IBusProjectService projectService;
@Resource
private ISysDictDataService dictDataService;
/**
* 查询施工进度计划
*
* @param id 主键
* @return 施工进度计划
*/
@Override
public DesConstructionSchedulePlanVo queryById(Long id) {
DesConstructionSchedulePlan constructionSchedulePlan = this.getById(id);
if (constructionSchedulePlan == null) {
throw new ServiceException("施工进度计划信息不存在", HttpStatus.NOT_FOUND);
}
return this.getVo(constructionSchedulePlan);
}
/**
* 查询符合条件的施工进度计划列表
*
* @param req 查询条件
* @return 施工进度计划列表
*/
@Override
public List<DesConstructionSchedulePlanVo> queryList(DesConstructionSchedulePlanQueryReq req) {
List<DesConstructionSchedulePlan> result = this.list(this.buildQueryWrapper(req));
return this.getVoList(result);
}
/**
* 新增施工进度计划
*
* @param req 施工进度计划
* @return 新增施工进度计划封装
*/
@Override
public DesConstructionSchedulePlanVo insertByBo(DesConstructionSchedulePlanCreateReq req) {
DesConstructionSchedulePlan constructionSchedulePlan = new DesConstructionSchedulePlan();
BeanUtils.copyProperties(req, constructionSchedulePlan);
boolean save = this.save(constructionSchedulePlan);
if (!save) {
throw new ServiceException("新增施工进度计划信息异常", HttpStatus.ERROR);
}
DesConstructionSchedulePlan newConstructionSchedulePlan = this.getById(constructionSchedulePlan.getId());
return this.getVo(newConstructionSchedulePlan);
}
/**
* 修改施工进度计划
*
* @param req 施工进度计划
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(DesConstructionSchedulePlanUpdateReq req) {
DesConstructionSchedulePlan constructionSchedulePlan = new DesConstructionSchedulePlan();
BeanUtils.copyProperties(req, constructionSchedulePlan);
return this.updateById(constructionSchedulePlan);
}
/**
* 修改施工进度计划为完成状态
*
* @param req 施工进度计划
* @return 是否修改成功
*/
@Override
public Boolean updateFinish(DesConstructionSchedulePlanFinishReq req) {
DesConstructionSchedulePlan plan = this.getById(req.getId());
if (plan == null) {
throw new ServiceException("施工进度计划信息不存在", HttpStatus.NOT_FOUND);
}
if (plan.getStatus().equals("4")) {
throw new ServiceException("施工进度计划已完成", HttpStatus.NOT_FOUND);
}
LocalDate practicalStartDate = plan.getPracticalStartDate();
LocalDate finishDate = req.getFinishDate();
if (practicalStartDate == null) {
throw new ServiceException("请先填写实际开始时间", HttpStatus.NOT_FOUND);
}
if (finishDate.isBefore(practicalStartDate)) {
throw new ServiceException("实际结束时间不能早于实际开始时间", HttpStatus.NOT_FOUND);
}
plan.setStatus("4");
plan.setPracticalEndDate(finishDate);
return this.updateById(plan);
}
/**
* 批量删除施工进度计划信息
*
* @param ids 待删除的主键集合
* @return 是否删除成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteByIds(Collection<Long> ids) {
return this.removeBatchByIds(ids);
}
/**
* 获取施工进度计划视图对象
*
* @param constructionSchedulePlan 施工进度计划对象
* @return 施工进度计划视图对象
*/
@Override
public DesConstructionSchedulePlanVo getVo(DesConstructionSchedulePlan constructionSchedulePlan) {
DesConstructionSchedulePlanVo vo = new DesConstructionSchedulePlanVo();
if (constructionSchedulePlan == null) {
return vo;
}
BeanUtils.copyProperties(constructionSchedulePlan, vo);
return vo;
}
/**
* 获取施工进度计划查询条件封装
*
* @param req 查询条件
* @return 查询条件封装
*/
@Override
public LambdaQueryWrapper<DesConstructionSchedulePlan> buildQueryWrapper(DesConstructionSchedulePlanQueryReq req) {
LambdaQueryWrapper<DesConstructionSchedulePlan> lqw = new LambdaQueryWrapper<>();
if (req == null) {
return lqw;
}
Long projectId = req.getProjectId();
String nodeName = req.getNodeName();
String status = req.getStatus();
lqw.eq(ObjectUtils.isNotEmpty(projectId), DesConstructionSchedulePlan::getProjectId, projectId);
lqw.like(StringUtils.isNotBlank(nodeName), DesConstructionSchedulePlan::getNodeName, nodeName);
lqw.eq(StringUtils.isNotBlank(status), DesConstructionSchedulePlan::getStatus, status);
return lqw;
}
/**
* 获取施工进度计划分页对象视图
*
* @param constructionSchedulePlanList 施工进度计划分页对象
* @return 施工进度计划分页对象视图
*/
@Override
public List<DesConstructionSchedulePlanVo> getVoList(List<DesConstructionSchedulePlan> constructionSchedulePlanList) {
return constructionSchedulePlanList.stream().map(this::getVo).toList();
}
// region 导出 excel
/**
* 导出Excel
*
* @param projectId 项目id
*/
@Override
public void exportExcelByProjectId(Long projectId, HttpServletResponse response) {
DesUserBo desUserBo = new DesUserBo();
desUserBo.setProjectId(projectId);
Map<Long, String> projectStructureMap = projectService.getStructureAsList(projectId);
if (projectStructureMap.isEmpty()) {
throw new ServiceException("获取项目列表失败,项目为空!!!");
}
List<SysDictDataVo> dictDataVos = dictDataService.selectByDictType("project_construction_status");
if (dictDataVos == null || dictDataVos.isEmpty()) {
throw new ServiceException("项目施工状态为空!!");
}
Map<String, String> statusMap = new HashMap<>();
for (SysDictDataVo vo : dictDataVos) {
statusMap.put(vo.getDictValue(), vo.getDictLabel());
}
Workbook workbook = new XSSFWorkbook();
// 创建主 Sheet 和隐藏 Sheet
Sheet mainSheet = workbook.createSheet("设计里程碑计划模版");
Sheet dataSheet = workbook.createSheet("DropdownData");
workbook.setSheetHidden(workbook.getSheetIndex(dataSheet), true);
// 3. 创建单元格样式
CellStyle editableStyle = createEditableCellStyle(workbook); // 可编辑单元格样式
CellStyle protectedStyle = createProtectedCellStyle(workbook); // 受保护单元格样式ID列用
//填充隐藏数据Sheet
int rowIndex = 0;
// 填充项目关联结构A列和B列
for (Map.Entry<Long, String> entry : projectStructureMap.entrySet()) {
Row row = dataSheet.createRow(rowIndex++);
row.createCell(0).setCellValue(entry.getValue());
row.createCell(1).setCellValue(entry.getKey().toString());
}
// 重置行索引填充人员和人员IDC列和D列
rowIndex = 0;
for (Map.Entry<String, String> entry : statusMap.entrySet()) {
Row row = dataSheet.getRow(rowIndex);
if (row == null) {
row = dataSheet.createRow(rowIndex);
}
row.createCell(4).setCellValue(entry.getValue());
row.createCell(5).setCellValue(entry.getKey());
rowIndex++;
}
// 主 Sheet 设置表头
Row sheetRow = mainSheet.createRow(0);
String[] headers = {"编号格式11.11.1.1。用于进行父子结构关联)", "节点名称",
"预计开始时间输入格式2025-09-06", "预计结束时间", "实际开始时间", "实际结束时间", "状态", "状态编码", "备注"};
for (int i = 0; i < headers.length; i++) {
Cell cell = sheetRow.createCell(i);
cell.setCellValue(headers[i]);
if (i == 0) {
CellStyle css = workbook.createCellStyle();
DataFormat format = workbook.createDataFormat();
css.setDataFormat(format.getFormat("@"));
cell.setCellStyle(css);
}
}
// 6. 设置专业下拉列表(第二列)
// setMajorDropdown(mainSheet, projectStructureMap.size());
setStatusDropdown(mainSheet, statusMap.size());
// String formulaTemplate = "IFERROR(INDEX(DropdownData!$B$1:$B$" + projectStructureMap.size() + ", MATCH(C{rowNum}, DropdownData!$A$1:$A$" + projectStructureMap.size() + ", 0)),\"\")";
String formulaTemplate1 = "IFERROR(INDEX(DropdownData!$F$1:$F$" + statusMap.size() + ", MATCH(G{rowNum}, DropdownData!$E$1:$E$" + statusMap.size() + ", 0)),\"\")";
for (int i = 1; i <= 1000; i++) { // 从第2行到101行
Row row = mainSheet.createRow(i);
int currentRowNum = i + 1; // Excel行号从1开始
// String formula = formulaTemplate.replace("{rowNum}", String.valueOf(currentRowNum));
//
// Cell cell = row.createCell(2);
// cell.setCellStyle(editableStyle); //专业不锁定
//
// Cell idCell = row.createCell(3);
// idCell.setCellFormula(formula);
// idCell.setCellStyle(protectedStyle); // 应用隐藏公式样式
String formula1 = formulaTemplate1.replace("{rowNum}", String.valueOf(currentRowNum));
Cell cell1 = row.createCell(6);
cell1.setCellStyle(editableStyle); //专业不锁定
Cell idCell1 = row.createCell(7);
idCell1.setCellFormula(formula1);
idCell1.setCellStyle(protectedStyle); // 应用隐藏公式样式
}
for (int i = 1; i <= 100; i++) {
Row row = mainSheet.getRow(i);
if (row == null) {
row = mainSheet.createRow(i);
}
Cell cell = row.createCell(0);
CellStyle textStyle = workbook.createCellStyle();
textStyle.setLocked(false);
DataFormat format = workbook.createDataFormat();
textStyle.setDataFormat(format.getFormat("@")); // "@" 表示文本格式
cell.setCellStyle(textStyle);
Cell cell1 = row.createCell(1);
cell1.setCellStyle(editableStyle);
Cell cell4 = row.createCell(2);
cell4.setCellStyle(editableStyle);
Cell cell5 = row.createCell(3);
cell5.setCellStyle(editableStyle);
Cell cell6 = row.createCell(4);
cell6.setCellStyle(editableStyle);
Cell cell7 = row.createCell(5);
cell7.setCellStyle(editableStyle);
Cell cell10 = row.createCell(8);
cell10.setCellStyle(editableStyle);
}
// 保护工作表,仅允许编辑未锁定的单元格
mainSheet.protectSheet("123456"); // 空密码
// 核心锁定表头第1行和前1列包含ID列
mainSheet.createFreezePane(0, 1, 0, 1);
// 调整列宽(更新列索引)
mainSheet.setColumnWidth(0, 20 * 320); // 编号
mainSheet.setColumnWidth(1, 20 * 280); // 节点名称
mainSheet.setColumnWidth(2, 20 * 320); // 预计开始时间
mainSheet.setColumnWidth(3, 20 * 200); // 预计结束时间
mainSheet.setColumnWidth(4, 20 * 200); // 实际开始时间
mainSheet.setColumnWidth(5, 20 * 200); // 实际结束时间
mainSheet.setColumnWidth(6, 20 * 100); // 状态
mainSheet.setColumnWidth(7, 20 * 200); // 状态编码
mainSheet.setColumnWidth(8, 20 * 200); // 备注
// 2. 设置响应头
// 设置响应头指定Excel格式和下载文件名
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("设计里程碑计划模版.xlsx", StandardCharsets.UTF_8));
// 直接写入响应输出流
try {
workbook.write(response.getOutputStream());
workbook.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 创建可编辑单元格样式
*/
private CellStyle createEditableCellStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setLocked(false); // 关键:允许编辑
return style;
}
/**
* 创建受保护单元格样式用于ID列
*/
private CellStyle createProtectedCellStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
DataFormat dataFormat = workbook.createDataFormat();
short formatIndex = dataFormat.getFormat("0"); // 匹配“自定义→0”格式
style.setDataFormat(formatIndex);
style.setHidden(true); // 隐藏公式
style.setLocked(true);
return style;
}
/**
* 设置专业下拉列表第二列索引1
*/
private void setMajorDropdown(Sheet mainSheet, int majorCount) {
DataValidationHelper helper = mainSheet.getDataValidationHelper();
// 专业数据范围数据Sheet的A列从第1行到专业数量行
String majorRange = "DropdownData!$A$1:$A$" + majorCount;
DataValidationConstraint constraint = helper.createFormulaListConstraint(majorRange);
// 作用范围第2行到100行第二列
CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, 2, 2);
DataValidation validation = helper.createValidation(constraint, addressList);
validation.setShowErrorBox(true);
mainSheet.addValidationData(validation);
}
/**
* 设置专业下拉列表第二列索引1
*/
private void setStatusDropdown(Sheet mainSheet, int majorCount) {
DataValidationHelper helper = mainSheet.getDataValidationHelper();
// 专业数据范围数据Sheet的A列从第1行到专业数量行
String majorRange = "DropdownData!$E$1:$E$" + majorCount;
DataValidationConstraint constraint = helper.createFormulaListConstraint(majorRange);
// 作用范围第2行到100行第二列
CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, 6, 6);
DataValidation validation = helper.createValidation(constraint, addressList);
validation.setShowErrorBox(true);
mainSheet.addValidationData(validation);
}
// endregion
/**
* 读取Excel文件
*
* @param file Excel文件
* @param projectId 项目ID
* @return 读取的数据列表
*/
@Override
public List<DesConstructionSchedulePlanExcelDto> readExcel(MultipartFile file, Long projectId) {
List<DesConstructionSchedulePlanExcelDto> dataList = new ArrayList<>();
try (InputStream inputStream = file.getInputStream();
XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
XSSFSheet sheet = workbook.getSheetAt(0);
// 从第二行(index=1)开始读取数据,跳过表头
for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
Row row = sheet.getRow(rowIndex);
if (hasValidData(row)) {
String number = getCellValue(row.getCell(0));
String nodeName = getCellValue(row.getCell(1));
String projectStructureName = null;
Long projectStructure = null;
LocalDate planStartDate = getLocalDateValue(row.getCell(2));
LocalDate planEndDate = getLocalDateValue(row.getCell(3));
LocalDate practicalStartDate = getLocalDateValue(row.getCell(4));
LocalDate practicalEndDate = getLocalDateValue(row.getCell(5));
String status = getCellValue(row.getCell(7));
String remark = getCellValue(row.getCell(8));
DesConstructionSchedulePlanExcelDto excelData = new DesConstructionSchedulePlanExcelDto(
number, projectId, nodeName, projectStructure, projectStructureName,
planStartDate, planEndDate, practicalStartDate, practicalEndDate, status, remark
);
dataList.add(excelData);
}
}
} catch (Exception e) {
log.error("读取表格数据失败", e);
throw new ServiceException("读取表格数据失败");
}
return dataList;
}
/**
* 将Excel数据转换为实体列表
*
* @param excelList Excel数据列表
* @return 实体列表
*/
@Override
public List<DesConstructionSchedulePlan> convertToEntities(List<DesConstructionSchedulePlanExcelDto> excelList) {
List<DesConstructionSchedulePlan> result = new ArrayList<>();
Map<String, Long> numberIdMap = new HashMap<>();
for (DesConstructionSchedulePlanExcelDto dto : excelList) {
DesConstructionSchedulePlan entity = new DesConstructionSchedulePlan();
LocalDate planStartDate = dto.getPlanStartDate();
LocalDate planEndDate = dto.getPlanEndDate();
if (planStartDate == null || planEndDate == null || planStartDate.isAfter(planEndDate)) {
throw new ServiceException("计划开始时间和计划结束时间不能为空,且计划开始时间不能大于计划结束时间", HttpStatus.BAD_REQUEST);
}
// 生成主键
Long id = IdWorker.getId();
entity.setId(id);
entity.setProjectId(dto.getProjectId());
entity.setNodeName(dto.getNodeName());
entity.setProjectStructure(dto.getProjectStructure());
entity.setProjectStructureName(dto.getProjectStructureName());
entity.setPlanStartDate(planStartDate);
entity.setPlanEndDate(planEndDate);
entity.setPracticalStartDate(dto.getPracticalStartDate());
entity.setPracticalEndDate(dto.getPracticalEndDate());
entity.setStatus(dto.getStatus());
entity.setRemark(dto.getRemark());
// 确定父ID
String number = dto.getNumber();
if (number != null && number.contains(".")) {
String parentNumber = number.substring(0, number.lastIndexOf("."));
Long parentId = numberIdMap.get(parentNumber);
if (parentId == null) {
throw new ServiceException("未找到父编号:" + parentNumber, HttpStatus.BAD_REQUEST);
}
entity.setParentId(parentId);
} else {
entity.setParentId(0L); // 顶级节点
}
// 保存当前编号对应的id
numberIdMap.put(number, id);
result.add(entity);
}
return result;
}
private static boolean hasValidData(Row row) {
// 遍历行中的所有单元格
for (int cellIndex = 0; cellIndex < row.getLastCellNum(); cellIndex++) {
Cell cell = row.getCell(cellIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
String cellValue = getCellValue(cell).trim();
// 只要有一个单元格有非空值,就认为是有效行
if (!cellValue.isEmpty()) {
return true;
}
}
return false;
}
private static String getCellValue(Cell cell) {
if (cell == null) {
return "";
}
// 使用CellType枚举判断单元格类型POI 4.0+版本推荐方式)
CellType cellType = cell.getCellType();
// 对于公式单元格,获取其计算结果的类型
if (cellType == CellType.FORMULA) {
cellType = cell.getCachedFormulaResultType();
}
switch (cellType) {
case STRING:
return cell.getStringCellValue().trim();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
return date.toString();
} else {
// 处理数字类型,避免科学计数法
// 处理数字,移除不必要的.0后缀
String numericValue = String.valueOf(cell.getNumericCellValue());
if (numericValue.endsWith(".0")) {
return numericValue.substring(0, numericValue.length() - 2);
}
return numericValue;
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
default:
return "";
}
}
private static LocalDate getLocalDateValue(Cell cell) {
if (cell == null) {
return null;
}
CellType cellType = cell.getCellType();
if (cellType == CellType.FORMULA) {
cellType = cell.getCachedFormulaResultType();
}
try {
if (cellType == CellType.NUMERIC) {
if (DateUtil.isCellDateFormatted(cell)) {
Date date = cell.getDateCellValue();
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
} else {
// 如果是数字但不是日期,就尝试转为 LocalDate (例如 20250730)
double numericValue = cell.getNumericCellValue();
String text = String.valueOf((long) numericValue);
return LocalDate.parse(text);
}
} else if (cellType == CellType.STRING) {
String text = cell.getStringCellValue().trim();
if (text.isEmpty()) {
return null;
}
// 尝试解析不同格式
try {
return LocalDate.parse(text); // 默认 ISO 格式 yyyy-MM-dd
} catch (Exception e) {
// 如果 Excel 是 yyyy/MM/dd 或 yyyy.MM.dd可以额外处理
try {
return LocalDate.parse(text, java.time.format.DateTimeFormatter.ofPattern("yyyy/MM/dd"));
} catch (Exception ignored) {
}
try {
return LocalDate.parse(text, java.time.format.DateTimeFormatter.ofPattern("yyyy.MM.dd"));
} catch (Exception ignored) {
}
return null; // 不识别的格式就返回 null
}
}
} catch (Exception e) {
return null;
}
return null;
}
}

View File

@ -302,7 +302,11 @@ public class PgsConstructionSchedulePlanServiceImpl extends ServiceImpl<PgsConst
row = mainSheet.createRow(i); row = mainSheet.createRow(i);
} }
Cell cell = row.createCell(0); Cell cell = row.createCell(0);
cell.setCellStyle(editableStyle); CellStyle textStyle = workbook.createCellStyle();
textStyle.setLocked(false);
DataFormat format = workbook.createDataFormat();
textStyle.setDataFormat(format.getFormat("@")); // "@" 表示文本格式
cell.setCellStyle(textStyle);
Cell cell1 = row.createCell(1); Cell cell1 = row.createCell(1);
cell1.setCellStyle(editableStyle); cell1.setCellStyle(editableStyle);