wuzhen update-cw
This commit is contained in:
1 parent
f625777484
commit
edaf8dbacf
45 files changed
+7606
-84
No files matched your search
+156
@@ -0,0 +1,156 @@
|
|||||||
|
package org.dromara.cw.controller;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||||
|
import org.dromara.common.log.annotation.Log;
|
||||||
|
import org.dromara.common.web.core.BaseController;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
import org.dromara.common.core.domain.R;
|
||||||
|
import org.dromara.cw.domain.bo.CwCustomApprovalBo;
|
||||||
|
import org.dromara.cw.domain.bo.CwApprovalActionBo;
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo;
|
||||||
|
import org.dromara.cw.domain.vo.CwApprovalRecordVo;
|
||||||
|
import org.dromara.cw.service.ICwProjectApprovalService;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程管理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/cw/projectApproval")
|
||||||
|
public class CwProjectApprovalController extends BaseController {
|
||||||
|
|
||||||
|
private final ICwProjectApprovalService projectApprovalService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动自定义审批流程
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectApproval:start")
|
||||||
|
@Log(title = "启动审批流程", businessType = BusinessType.INSERT)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PostMapping("/start")
|
||||||
|
public R<Void> startApprovalProcess(@Validated @RequestBody CwCustomApprovalBo bo) {
|
||||||
|
// 设置提交人信息
|
||||||
|
bo.setSubmitterId(LoginHelper.getUserId());
|
||||||
|
bo.setSubmitterName(LoginHelper.getUsername());
|
||||||
|
return toAjax(projectApprovalService.startCustomApprovalProcess(bo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行审批操作
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectApproval:approve")
|
||||||
|
@Log(title = "执行审批操作", businessType = BusinessType.UPDATE)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PutMapping("/approve")
|
||||||
|
public R<Void> executeApproval(@Validated @RequestBody CwApprovalActionBo bo) {
|
||||||
|
return toAjax(projectApprovalService.executeApproval(bo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的待审批列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectApproval:query")
|
||||||
|
@GetMapping("/pending")
|
||||||
|
public TableDataInfo<CwApprovalRecordVo> getMyPendingApprovals(PageQuery pageQuery) {
|
||||||
|
return projectApprovalService.getMyPendingApprovals(pageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的已审批列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectApproval:query")
|
||||||
|
@GetMapping("/processed")
|
||||||
|
public TableDataInfo<CwApprovalRecordVo> getMyProcessedApprovals(PageQuery pageQuery) {
|
||||||
|
return projectApprovalService.getMyProcessedApprovals(pageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询审批流程列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectApproval:query")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo<CwProjectApprovalProcessVo> list(PageQuery pageQuery) {
|
||||||
|
return projectApprovalService.queryProcessList(pageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审批流程详情
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectApproval:query")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public R<CwProjectApprovalProcessVo> getInfo(@NotNull(message = "主键不能为空")
|
||||||
|
@PathVariable Long id) {
|
||||||
|
return R.ok(projectApprovalService.getProcessDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审批记录列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectApproval:query")
|
||||||
|
@GetMapping("/records/{applicationId}")
|
||||||
|
public R<List<CwApprovalRecordVo>> getApprovalRecords(@PathVariable Long applicationId) {
|
||||||
|
return R.ok(projectApprovalService.getApprovalRecords(applicationId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终止审批流程
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectApproval:terminate")
|
||||||
|
@Log(title = "终止审批流程", businessType = BusinessType.UPDATE)
|
||||||
|
@DeleteMapping("/{ids}")
|
||||||
|
public R<Void> terminate(@NotEmpty(message = "主键不能为空")
|
||||||
|
@PathVariable Long[] ids) {
|
||||||
|
return toAjax(projectApprovalService.terminateProcess(List.of(ids)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程验证
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectApproval:query")
|
||||||
|
@PostMapping("/validate")
|
||||||
|
public R<String> validateApprovalProcess(@RequestBody CwCustomApprovalBo bo) {
|
||||||
|
// 简单验证逻辑
|
||||||
|
if (bo.getAuditorIds() == null || bo.getAuditorIds().isEmpty()) {
|
||||||
|
return R.fail("请选择审核人");
|
||||||
|
}
|
||||||
|
if (bo.getAuditorIds().size() > 10) {
|
||||||
|
return R.fail("审核人数量不能超过10个");
|
||||||
|
}
|
||||||
|
return R.ok("验证通过");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批选项查询(用于下拉选择)
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectApproval:query")
|
||||||
|
@GetMapping("/options")
|
||||||
|
public R<Object> getApprovalOptions() {
|
||||||
|
// 返回一些选项数据,比如审批动作选项等
|
||||||
|
return R.ok(java.util.Map.of(
|
||||||
|
"actions", java.util.List.of(
|
||||||
|
java.util.Map.of("value", "approve", "label", "通过"),
|
||||||
|
java.util.Map.of("value", "reject", "label", "拒绝")
|
||||||
|
),
|
||||||
|
"statuses", java.util.List.of(
|
||||||
|
java.util.Map.of("value", "waiting", "label", "等待审批"),
|
||||||
|
java.util.Map.of("value", "approved", "label", "已通过"),
|
||||||
|
java.util.Map.of("value", "rejected", "label", "已拒绝")
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+251
@@ -0,0 +1,251 @@
|
|||||||
|
package org.dromara.cw.controller;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||||
|
import org.dromara.common.log.annotation.Log;
|
||||||
|
import org.dromara.common.web.core.BaseController;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
import org.dromara.common.core.domain.R;
|
||||||
|
import org.dromara.common.core.validate.AddGroup;
|
||||||
|
import org.dromara.common.core.validate.EditGroup;
|
||||||
|
import org.dromara.common.excel.utils.ExcelUtil;
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectAuditorVo;
|
||||||
|
import org.dromara.cw.domain.bo.CwProjectAuditorBo;
|
||||||
|
import org.dromara.cw.service.ICwProjectAuditorService;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审核人配置
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/cw/projectAuditor")
|
||||||
|
public class CwProjectAuditorController extends BaseController {
|
||||||
|
|
||||||
|
private final ICwProjectAuditorService cwProjectAuditorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审核人配置列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:list")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo<CwProjectAuditorVo> list(CwProjectAuditorBo bo, PageQuery pageQuery) {
|
||||||
|
return cwProjectAuditorService.queryPageList(bo, pageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出项目审核人配置列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:export")
|
||||||
|
@Log(title = "项目审核人配置", businessType = BusinessType.EXPORT)
|
||||||
|
@PostMapping("/export")
|
||||||
|
public void export(CwProjectAuditorBo bo, HttpServletResponse response) {
|
||||||
|
List<CwProjectAuditorVo> list = cwProjectAuditorService.queryList(bo);
|
||||||
|
ExcelUtil.exportExcel(list, "项目审核人配置", CwProjectAuditorVo.class, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取项目审核人配置详细信息
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:query")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public R<CwProjectAuditorVo> getInfo(@NotNull(message = "主键不能为空")
|
||||||
|
@PathVariable Long id) {
|
||||||
|
return R.ok(cwProjectAuditorService.queryById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增项目审核人配置
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:add")
|
||||||
|
@Log(title = "项目审核人配置", businessType = BusinessType.INSERT)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PostMapping()
|
||||||
|
public R<Void> add(@Validated(AddGroup.class) @RequestBody CwProjectAuditorBo bo) {
|
||||||
|
// 设置配置人信息
|
||||||
|
bo.setConfigBy(LoginHelper.getUserId());
|
||||||
|
bo.setConfigByName(LoginHelper.getUsername());
|
||||||
|
return toAjax(cwProjectAuditorService.insertByBo(bo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改项目审核人配置
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:edit")
|
||||||
|
@Log(title = "项目审核人配置", businessType = BusinessType.UPDATE)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PutMapping()
|
||||||
|
public R<Void> edit(@Validated(EditGroup.class) @RequestBody CwProjectAuditorBo bo) {
|
||||||
|
return toAjax(cwProjectAuditorService.updateByBo(bo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除项目审核人配置
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:remove")
|
||||||
|
@Log(title = "项目审核人配置", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{ids}")
|
||||||
|
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||||
|
@PathVariable Long[] ids) {
|
||||||
|
return toAjax(cwProjectAuditorService.deleteWithValidByIds(List.of(ids), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加审核人
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:add")
|
||||||
|
@Log(title = "批量添加审核人", businessType = BusinessType.INSERT)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PostMapping("/batchAdd")
|
||||||
|
public R<Void> batchAdd(@RequestBody BatchAddRequest request) {
|
||||||
|
return toAjax(cwProjectAuditorService.batchAddAuditors(
|
||||||
|
request.getUserIds(),
|
||||||
|
LoginHelper.getUserId(),
|
||||||
|
LoginHelper.getUsername(),
|
||||||
|
request.getRemark()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取可用审核人列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:list")
|
||||||
|
@GetMapping("/available")
|
||||||
|
public R<List<CwProjectAuditorVo>> getAvailableAuditors() {
|
||||||
|
return R.ok(cwProjectAuditorService.getAvailableAuditors());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据部门ID获取用户列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:list")
|
||||||
|
@GetMapping("/deptUsers/{deptId}")
|
||||||
|
public R<List<Object>> getDeptUsers(@PathVariable Long deptId) {
|
||||||
|
// 参数验证
|
||||||
|
if (deptId == null || deptId <= 0) {
|
||||||
|
return R.fail("部门ID参数无效");
|
||||||
|
}
|
||||||
|
return R.ok(cwProjectAuditorService.getDeptUsers(deptId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新状态
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:edit")
|
||||||
|
@Log(title = "批量更新审核人状态", businessType = BusinessType.UPDATE)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PutMapping("/status")
|
||||||
|
public R<Void> updateStatus(@RequestBody StatusUpdateRequest request) {
|
||||||
|
return toAjax(cwProjectAuditorService.updateStatus(request.getIds(), request.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 我配置的审核人
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:list")
|
||||||
|
@GetMapping("/myConfig")
|
||||||
|
public TableDataInfo<CwProjectAuditorVo> getMyConfig(CwProjectAuditorBo bo, PageQuery pageQuery) {
|
||||||
|
bo.setConfigBy(LoginHelper.getUserId());
|
||||||
|
return cwProjectAuditorService.queryPageList(bo, pageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审核人选项(用于下拉选择)
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:list")
|
||||||
|
@GetMapping("/options")
|
||||||
|
public R<List<CwProjectAuditorVo>> getAuditorOptions() {
|
||||||
|
return R.ok(cwProjectAuditorService.getAvailableAuditors());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量启用
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:edit")
|
||||||
|
@Log(title = "批量启用审核人", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping("/enable/{ids}")
|
||||||
|
public R<Void> enable(@PathVariable Long[] ids) {
|
||||||
|
return toAjax(cwProjectAuditorService.updateStatus(List.of(ids), "1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量停用
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:edit")
|
||||||
|
@Log(title = "批量停用审核人", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping("/disable/{ids}")
|
||||||
|
public R<Void> disable(@PathVariable Long[] ids) {
|
||||||
|
return toAjax(cwProjectAuditorService.updateStatus(List.of(ids), "0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审核人统计数据
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("cw:projectAuditor:list")
|
||||||
|
@GetMapping("/statistics")
|
||||||
|
public R<Object> getStatistics() {
|
||||||
|
return R.ok(cwProjectAuditorService.getStatistics());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加请求对象
|
||||||
|
*/
|
||||||
|
public static class BatchAddRequest {
|
||||||
|
private List<Long> userIds;
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
public List<Long> getUserIds() {
|
||||||
|
return userIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserIds(List<Long> userIds) {
|
||||||
|
this.userIds = userIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemark() {
|
||||||
|
return remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemark(String remark) {
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态更新请求对象
|
||||||
|
*/
|
||||||
|
public static class StatusUpdateRequest {
|
||||||
|
private List<Long> ids;
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
public List<Long> getIds() {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIds(List<Long> ids) {
|
||||||
|
this.ids = ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+15
@@ -124,4 +124,19 @@ public class CwApprovalRecord extends TenantEntity implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String configSnapshot;
|
private String configSnapshot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程ID
|
||||||
|
*/
|
||||||
|
private Long processId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义节点名称
|
||||||
|
*/
|
||||||
|
private String customNodeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否自定义流程
|
||||||
|
*/
|
||||||
|
private Boolean isCustomProcess;
|
||||||
|
|
||||||
}
|
}
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
package org.dromara.cw.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.dromara.common.tenant.core.TenantEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程对象 cw_project_approval_process
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("cw_project_approval_process")
|
||||||
|
public class CwProjectApprovalProcess extends TenantEntity implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID(雪花ID)
|
||||||
|
*/
|
||||||
|
@TableId(value = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申报ID
|
||||||
|
*/
|
||||||
|
private Long applicationId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申报编号
|
||||||
|
*/
|
||||||
|
private String applicationNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程名称
|
||||||
|
*/
|
||||||
|
private String processName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程状态(waiting-等待开始 running-进行中 completed-已完成 terminated-已终止)
|
||||||
|
*/
|
||||||
|
private String processStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总审批节点数
|
||||||
|
*/
|
||||||
|
private Integer totalNodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前审批节点
|
||||||
|
*/
|
||||||
|
private Integer currentNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程开始时间
|
||||||
|
*/
|
||||||
|
private Date startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程结束时间
|
||||||
|
*/
|
||||||
|
private Date endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交人ID
|
||||||
|
*/
|
||||||
|
private Long submitterId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交人姓名
|
||||||
|
*/
|
||||||
|
private String submitterName;
|
||||||
|
|
||||||
|
}
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
package org.dromara.cw.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.dromara.common.tenant.core.TenantEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审核人配置对象 cw_project_auditor
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("cw_project_auditor")
|
||||||
|
public class CwProjectAuditor extends TenantEntity implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID(雪花ID)
|
||||||
|
*/
|
||||||
|
@TableId(value = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户姓名
|
||||||
|
*/
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
*/
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属部门ID
|
||||||
|
*/
|
||||||
|
private Long deptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属部门名称
|
||||||
|
*/
|
||||||
|
private String deptName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 联系电话
|
||||||
|
*/
|
||||||
|
private String phoneNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱地址
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核人状态(1正常 0停用)
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注说明
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置人ID
|
||||||
|
*/
|
||||||
|
private Long configBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置人姓名
|
||||||
|
*/
|
||||||
|
private String configByName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除标志(0代表存在 1代表删除)
|
||||||
|
*/
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package org.dromara.cw.domain.bo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批操作业务对象
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CwApprovalActionBo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批记录ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "审批记录ID不能为空")
|
||||||
|
private Long recordId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批操作(approve-通过 reject-拒绝)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "审批操作不能为空")
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批意见
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "审批意见不能为空")
|
||||||
|
private String opinion;
|
||||||
|
|
||||||
|
}
|
||||||
+5
@@ -139,6 +139,11 @@ public class CwApprovalRecordBo extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private String deptId;
|
private String deptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门ID(Long类型,用于数据库操作)
|
||||||
|
*/
|
||||||
|
private Long deptIdLong;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 项目名称(查询条件)
|
* 项目名称(查询条件)
|
||||||
*/
|
*/
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
package org.dromara.cw.domain.bo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义审批流程业务对象
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CwCustomApprovalBo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申报ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "申报ID不能为空")
|
||||||
|
private Long applicationId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申报编号
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "申报编号不能为空")
|
||||||
|
private String applicationNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核人ID列表
|
||||||
|
*/
|
||||||
|
@NotEmpty(message = "审核人列表不能为空")
|
||||||
|
private List<Long> auditorIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程名称
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "流程名称不能为空")
|
||||||
|
private String processName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交人ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "提交人ID不能为空")
|
||||||
|
private Long submitterId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交人姓名
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "提交人姓名不能为空")
|
||||||
|
private String submitterName;
|
||||||
|
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
package org.dromara.cw.domain.bo;
|
||||||
|
|
||||||
|
import org.dromara.cw.domain.CwProjectApprovalProcess;
|
||||||
|
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||||
|
import org.dromara.common.core.validate.AddGroup;
|
||||||
|
import org.dromara.common.core.validate.EditGroup;
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程业务对象 cw_project_approval_process
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@AutoMapper(target = CwProjectApprovalProcess.class, reverseConvertGenerate = false)
|
||||||
|
public class CwProjectApprovalProcessBo extends BaseEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID(雪花ID)
|
||||||
|
*/
|
||||||
|
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申报ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "申报ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private Long applicationId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申报编号
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "申报编号不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String applicationNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程名称
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "流程名称不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String processName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程状态(waiting-等待开始 running-进行中 completed-已完成 terminated-已终止)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "流程状态不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String processStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总审批节点数
|
||||||
|
*/
|
||||||
|
@NotNull(message = "总审批节点数不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private Integer totalNodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前审批节点
|
||||||
|
*/
|
||||||
|
private Integer currentNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程开始时间
|
||||||
|
*/
|
||||||
|
private Date startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程结束时间
|
||||||
|
*/
|
||||||
|
private Date endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交人ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "提交人ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private Long submitterId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交人姓名
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "提交人姓名不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String submitterName;
|
||||||
|
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package org.dromara.cw.domain.bo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加审核人业务对象
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CwProjectAuditorBatchBo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID列表
|
||||||
|
*/
|
||||||
|
@NotEmpty(message = "用户ID列表不能为空")
|
||||||
|
private List<Long> userIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置人ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "配置人ID不能为空")
|
||||||
|
private Long configBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置人姓名
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "配置人姓名不能为空")
|
||||||
|
private String configByName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注说明
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
package org.dromara.cw.domain.bo;
|
||||||
|
|
||||||
|
import org.dromara.cw.domain.CwProjectAuditor;
|
||||||
|
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||||
|
import org.dromara.common.core.validate.AddGroup;
|
||||||
|
import org.dromara.common.core.validate.EditGroup;
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审核人配置业务对象 cw_project_auditor
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@AutoMapper(target = CwProjectAuditor.class, reverseConvertGenerate = false)
|
||||||
|
public class CwProjectAuditorBo extends BaseEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID(雪花ID)
|
||||||
|
*/
|
||||||
|
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户姓名
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "用户姓名不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
*/
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属部门ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "所属部门ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private Long deptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属部门名称
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "所属部门名称不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String deptName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 联系电话
|
||||||
|
*/
|
||||||
|
private String phoneNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱地址
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核人状态(1正常 0停用)
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "审核人状态不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注说明
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置人ID
|
||||||
|
*/
|
||||||
|
private Long configBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置人姓名
|
||||||
|
*/
|
||||||
|
private String configByName;
|
||||||
|
|
||||||
|
}
|
||||||
+24
@@ -232,4 +232,28 @@ public class CwApprovalRecordVo implements Serializable {
|
|||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date approvalTime;
|
private Date approvalTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总节点数(从流程表获取)
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "总节点数")
|
||||||
|
private Integer totalNodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前节点数(从流程表获取)
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "当前节点数")
|
||||||
|
private Integer currentNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程名称(从流程表获取)
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "流程名称")
|
||||||
|
private String processName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程状态(从流程表获取)
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "流程状态")
|
||||||
|
private String processStatus;
|
||||||
|
|
||||||
}
|
}
|
||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
package org.dromara.cw.domain.vo;
|
||||||
|
|
||||||
|
import org.dromara.cw.domain.CwProjectApprovalProcess;
|
||||||
|
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
|
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||||
|
import org.dromara.common.excel.convert.ExcelDictConvert;
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import lombok.Data;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程视图对象 cw_project_approval_process
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ExcelIgnoreUnannotated
|
||||||
|
@AutoMapper(target = CwProjectApprovalProcess.class)
|
||||||
|
public class CwProjectApprovalProcessVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID(雪花ID)
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "主键ID")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申报ID
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "申报ID")
|
||||||
|
private Long applicationId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 申报编号
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "申报编号")
|
||||||
|
private String applicationNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程名称
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "流程名称")
|
||||||
|
private String processName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程状态(waiting-等待开始 running-进行中 completed-已完成 terminated-已终止)
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "流程状态", converter = ExcelDictConvert.class)
|
||||||
|
@ExcelDictFormat(dictType = "cw_process_status")
|
||||||
|
private String processStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总审批节点数
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "总审批节点数")
|
||||||
|
private Integer totalNodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前审批节点
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "当前审批节点")
|
||||||
|
private Integer currentNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程开始时间
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "流程开始时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程结束时间
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "流程结束时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交人ID
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "提交人ID")
|
||||||
|
private Long submitterId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交人姓名
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "提交人姓名")
|
||||||
|
private String submitterName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
private Long createBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者
|
||||||
|
*/
|
||||||
|
private Long updateBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
}
|
||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
package org.dromara.cw.domain.vo;
|
||||||
|
|
||||||
|
import org.dromara.cw.domain.CwProjectAuditor;
|
||||||
|
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
|
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||||
|
import org.dromara.common.excel.convert.ExcelDictConvert;
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import lombok.Data;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审核人配置视图对象 cw_project_auditor
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ExcelIgnoreUnannotated
|
||||||
|
@AutoMapper(target = CwProjectAuditor.class)
|
||||||
|
public class CwProjectAuditorVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID(雪花ID)
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "主键ID")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "用户ID")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户姓名
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "用户姓名")
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "用户昵称")
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属部门ID
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "所属部门ID")
|
||||||
|
private Long deptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属部门名称
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "所属部门名称")
|
||||||
|
private String deptName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 联系电话
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "联系电话")
|
||||||
|
private String phoneNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱地址
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "邮箱地址")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核人状态(1正常 0停用)
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "审核人状态", converter = ExcelDictConvert.class)
|
||||||
|
@ExcelDictFormat(dictType = "sys_normal_disable")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注说明
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "备注说明")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置人ID
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "配置人ID")
|
||||||
|
private Long configBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置人姓名
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "配置人姓名")
|
||||||
|
private String configByName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
private Long createBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者
|
||||||
|
*/
|
||||||
|
private Long updateBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package org.dromara.cw.mapper;
|
||||||
|
|
||||||
|
import org.dromara.cw.domain.CwProjectApprovalProcess;
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo;
|
||||||
|
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程Mapper接口
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
public interface CwProjectApprovalProcessMapper extends BaseMapperPlus<CwProjectApprovalProcess, CwProjectApprovalProcessVo> {
|
||||||
|
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package org.dromara.cw.mapper;
|
||||||
|
|
||||||
|
import org.dromara.cw.domain.CwProjectAuditor;
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectAuditorVo;
|
||||||
|
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审核人配置Mapper接口
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
public interface CwProjectAuditorMapper extends BaseMapperPlus<CwProjectAuditor, CwProjectAuditorVo> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据部门ID查询可用审核人
|
||||||
|
*/
|
||||||
|
List<CwProjectAuditorVo> selectAuditorsByDeptId(@Param("deptId") Long deptId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询审核人统计信息
|
||||||
|
*/
|
||||||
|
List<CwProjectAuditorVo> selectAuditorStatistics();
|
||||||
|
|
||||||
|
}
|
||||||
+20
@@ -2,6 +2,7 @@ package org.dromara.cw.service;
|
|||||||
|
|
||||||
import org.dromara.cw.domain.bo.CwApprovalRecordBo;
|
import org.dromara.cw.domain.bo.CwApprovalRecordBo;
|
||||||
import org.dromara.cw.domain.vo.CwApprovalRecordVo;
|
import org.dromara.cw.domain.vo.CwApprovalRecordVo;
|
||||||
|
import org.dromara.cw.domain.CwApprovalRecord;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
|
||||||
@@ -119,6 +120,11 @@ public interface ICwApprovalRecordService {
|
|||||||
*/
|
*/
|
||||||
Boolean insertByBo(CwApprovalRecordBo bo);
|
Boolean insertByBo(CwApprovalRecordBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量新增审批记录
|
||||||
|
*/
|
||||||
|
Boolean batchInsert(List<CwApprovalRecord> records);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改审批记录
|
* 修改审批记录
|
||||||
*/
|
*/
|
||||||
@@ -190,4 +196,18 @@ public interface ICwApprovalRecordService {
|
|||||||
*/
|
*/
|
||||||
List<Map<String, Object>> getAvailableApprovers(Long applicationId);
|
List<Map<String, Object>> getAvailableApprovers(Long applicationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建自定义审批流程
|
||||||
|
* 根据用户选择的审批人列表创建自定义审批流程
|
||||||
|
*
|
||||||
|
* @param applicationId 申报ID
|
||||||
|
* @param applicationNo 申报编号
|
||||||
|
* @param auditorIds 审批人ID列表(按审批顺序排列)
|
||||||
|
* @param submitterId 提交人ID
|
||||||
|
* @param submitterName 提交人名称
|
||||||
|
* @return 是否创建成功
|
||||||
|
*/
|
||||||
|
Boolean createCustomApprovalProcess(Long applicationId, String applicationNo,
|
||||||
|
List<Long> auditorIds, Long submitterId, String submitterName);
|
||||||
|
|
||||||
}
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
package org.dromara.cw.service;
|
||||||
|
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo;
|
||||||
|
import org.dromara.cw.domain.bo.CwCustomApprovalBo;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程Service接口
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
public interface ICwProjectApprovalProcessService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审批流程
|
||||||
|
*/
|
||||||
|
CwProjectApprovalProcessVo queryById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审批流程列表
|
||||||
|
*/
|
||||||
|
TableDataInfo<CwProjectApprovalProcessVo> queryPageList(CwProjectApprovalProcessVo vo, PageQuery pageQuery);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审批流程列表
|
||||||
|
*/
|
||||||
|
List<CwProjectApprovalProcessVo> queryList(CwProjectApprovalProcessVo vo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据申报ID查询审批流程
|
||||||
|
*/
|
||||||
|
CwProjectApprovalProcessVo queryByApplicationId(Long applicationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据申报编号查询审批流程
|
||||||
|
*/
|
||||||
|
CwProjectApprovalProcessVo queryByApplicationNo(String applicationNo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询我提交的审批流程
|
||||||
|
*/
|
||||||
|
List<CwProjectApprovalProcessVo> queryMyProcesses(Long submitterId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询进行中的审批流程
|
||||||
|
*/
|
||||||
|
List<CwProjectApprovalProcessVo> queryRunningProcesses();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询超时的审批流程
|
||||||
|
*/
|
||||||
|
List<CwProjectApprovalProcessVo> queryOvertimeProcesses(Integer hours);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动自定义审批流程
|
||||||
|
*/
|
||||||
|
Boolean startCustomApprovalProcess(CwCustomApprovalBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终止审批流程
|
||||||
|
*/
|
||||||
|
Boolean terminateProcess(Long id, String reason);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验并批量删除项目审批流程信息
|
||||||
|
*/
|
||||||
|
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推进流程到下一节点
|
||||||
|
*/
|
||||||
|
Boolean advanceToNextNode(Long processId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成审批流程
|
||||||
|
*/
|
||||||
|
Boolean completeProcess(Long processId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新流程当前节点
|
||||||
|
*/
|
||||||
|
Boolean updateCurrentNode(Long id, Integer currentNode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新流程状态
|
||||||
|
*/
|
||||||
|
Boolean updateProcessStatus(Long id, String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证审批流程配置
|
||||||
|
*/
|
||||||
|
Boolean validateApprovalProcess(CwCustomApprovalBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计审批流程状态
|
||||||
|
*/
|
||||||
|
List<Map<String, Object>> queryProcessStatistics(Long submitterId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出数据
|
||||||
|
*/
|
||||||
|
List<CwProjectApprovalProcessVo> exportList(CwProjectApprovalProcessVo vo);
|
||||||
|
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
package org.dromara.cw.service;
|
||||||
|
|
||||||
|
import org.dromara.cw.domain.bo.CwCustomApprovalBo;
|
||||||
|
import org.dromara.cw.domain.bo.CwApprovalActionBo;
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo;
|
||||||
|
import org.dromara.cw.domain.vo.CwApprovalRecordVo;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程Service接口
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
public interface ICwProjectApprovalService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动自定义审批流程
|
||||||
|
*/
|
||||||
|
Boolean startCustomApprovalProcess(CwCustomApprovalBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行审批操作
|
||||||
|
*/
|
||||||
|
Boolean executeApproval(CwApprovalActionBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的待审批列表
|
||||||
|
*/
|
||||||
|
TableDataInfo<CwApprovalRecordVo> getMyPendingApprovals(PageQuery pageQuery);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的已审批列表
|
||||||
|
*/
|
||||||
|
TableDataInfo<CwApprovalRecordVo> getMyProcessedApprovals(PageQuery pageQuery);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询审批流程列表
|
||||||
|
*/
|
||||||
|
TableDataInfo<CwProjectApprovalProcessVo> queryProcessList(PageQuery pageQuery);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审批流程详情
|
||||||
|
*/
|
||||||
|
CwProjectApprovalProcessVo getProcessDetail(Long processId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审批记录列表
|
||||||
|
*/
|
||||||
|
List<CwApprovalRecordVo> getApprovalRecords(Long applicationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终止审批流程
|
||||||
|
*/
|
||||||
|
Boolean terminateProcess(List<Long> processIds);
|
||||||
|
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
package org.dromara.cw.service;
|
||||||
|
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectAuditorVo;
|
||||||
|
import org.dromara.cw.domain.bo.CwProjectAuditorBo;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审核人配置Service接口
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
public interface ICwProjectAuditorService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审核人配置
|
||||||
|
*/
|
||||||
|
CwProjectAuditorVo queryById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审核人配置列表
|
||||||
|
*/
|
||||||
|
TableDataInfo<CwProjectAuditorVo> queryPageList(CwProjectAuditorBo bo, PageQuery pageQuery);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审核人配置列表
|
||||||
|
*/
|
||||||
|
List<CwProjectAuditorVo> queryList(CwProjectAuditorBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增项目审核人配置
|
||||||
|
*/
|
||||||
|
Boolean insertByBo(CwProjectAuditorBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改项目审核人配置
|
||||||
|
*/
|
||||||
|
Boolean updateByBo(CwProjectAuditorBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验并批量删除项目审核人配置信息
|
||||||
|
*/
|
||||||
|
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加审核人(从部门用户中选择)
|
||||||
|
*/
|
||||||
|
Boolean batchAddAuditors(List<Long> userIds, Long configBy, String configByName, String remark);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取可用审核人列表
|
||||||
|
*/
|
||||||
|
List<CwProjectAuditorVo> getAvailableAuditors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据部门ID获取用户列表
|
||||||
|
*/
|
||||||
|
List<Object> getDeptUsers(Long deptId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新状态
|
||||||
|
*/
|
||||||
|
Boolean updateStatus(List<Long> ids, String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审核人统计数据
|
||||||
|
*/
|
||||||
|
Object getStatistics();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID查询项目审核人配置
|
||||||
|
*/
|
||||||
|
CwProjectAuditorVo queryByUserId(Long userId);
|
||||||
|
|
||||||
|
}
|
||||||
+232
-18
@@ -5,13 +5,12 @@ import org.dromara.common.core.utils.StringUtils;
|
|||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||||
import org.dromara.common.redis.utils.SequenceUtils;
|
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -25,8 +24,10 @@ import org.dromara.cw.domain.CwApprovalRecord;
|
|||||||
import org.dromara.cw.mapper.CwApprovalRecordMapper;
|
import org.dromara.cw.mapper.CwApprovalRecordMapper;
|
||||||
import org.dromara.cw.service.ICwApprovalRecordService;
|
import org.dromara.cw.service.ICwApprovalRecordService;
|
||||||
import org.dromara.cw.service.ICwProjectApplicationService;
|
import org.dromara.cw.service.ICwProjectApplicationService;
|
||||||
|
import org.dromara.cw.service.ICwProjectAuditorService;
|
||||||
import org.dromara.cw.mapper.CwProjectApplicationMapper;
|
import org.dromara.cw.mapper.CwProjectApplicationMapper;
|
||||||
import org.dromara.cw.domain.CwProjectApplication;
|
import org.dromara.cw.domain.CwProjectApplication;
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectAuditorVo;
|
||||||
import org.dromara.system.service.ISysUserService;
|
import org.dromara.system.service.ISysUserService;
|
||||||
import org.dromara.system.service.ISysDeptService;
|
import org.dromara.system.service.ISysDeptService;
|
||||||
import org.dromara.system.domain.vo.SysUserVo;
|
import org.dromara.system.domain.vo.SysUserVo;
|
||||||
@@ -54,6 +55,7 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
|
|||||||
private final CwProjectApplicationMapper applicationMapper;
|
private final CwProjectApplicationMapper applicationMapper;
|
||||||
private final ISysUserService sysUserService;
|
private final ISysUserService sysUserService;
|
||||||
private final ISysDeptService sysDeptService;
|
private final ISysDeptService sysDeptService;
|
||||||
|
private final ICwProjectAuditorService projectAuditorService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据主键查询审批记录
|
* 根据主键查询审批记录
|
||||||
@@ -502,6 +504,17 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
|
|||||||
return flag;
|
return flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量新增审批记录
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean batchInsert(List<CwApprovalRecord> records) {
|
||||||
|
if (records == null || records.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return baseMapper.batchInsert(records) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改审批记录
|
* 修改审批记录
|
||||||
*/
|
*/
|
||||||
@@ -678,8 +691,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
|
|||||||
Long currentDeptId, String currentTenantId) {
|
Long currentDeptId, String currentTenantId) {
|
||||||
CwApprovalRecord record = new CwApprovalRecord();
|
CwApprovalRecord record = new CwApprovalRecord();
|
||||||
|
|
||||||
// 生成唯一ID(批量插入需要手动生成)
|
// 移除手动ID设置,使用MyBatis-Plus自动生成雪花ID(符合项目规范)
|
||||||
record.setId(SequenceUtils.nextId("approval_record", Duration.ofDays(1)));
|
// record.setId() - 让MyBatis-Plus自动生成,避免主键冲突
|
||||||
|
|
||||||
// 设置申报相关信息
|
// 设置申报相关信息
|
||||||
record.setApplicationId(applicationId);
|
record.setApplicationId(applicationId);
|
||||||
@@ -979,27 +992,110 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Boolean updateApplicationStatusAndProcess(Long applicationId, String nodeCode, String approveAction) {
|
private Boolean updateApplicationStatusAndProcess(Long applicationId, String nodeCode, String approveAction) {
|
||||||
if ("approve".equals(approveAction)) {
|
try {
|
||||||
// 审批通过,启动下一个节点或完成审批
|
// 获取当前审批记录
|
||||||
if ("dept_approve".equals(nodeCode)) {
|
LambdaQueryWrapper<CwApprovalRecord> currentQuery = Wrappers.<CwApprovalRecord>lambdaQuery()
|
||||||
// 部门审批通过,启动财务审批
|
.eq(CwApprovalRecord::getApplicationId, applicationId)
|
||||||
return startNextApprovalNode(applicationId, "finance_approve");
|
.eq(CwApprovalRecord::getNodeCode, nodeCode);
|
||||||
} else if ("finance_approve".equals(nodeCode)) {
|
CwApprovalRecord currentRecord = baseMapper.selectOne(currentQuery);
|
||||||
// 财务审批通过,启动领导审批
|
|
||||||
return startNextApprovalNode(applicationId, "leader_approve");
|
if (currentRecord == null) {
|
||||||
} else if ("leader_approve".equals(nodeCode)) {
|
log.error("❌ 当前审批记录不存在:applicationId={}, nodeCode={}", applicationId, nodeCode);
|
||||||
// 领导审批通过,整个审批流程完成
|
return false;
|
||||||
return projectApplicationService.updateApprovalStatus(applicationId, "approved");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("approve".equals(approveAction)) {
|
||||||
|
// 🔧 基于node_order的顺序审批逻辑
|
||||||
|
log.info("✅ 审批通过,查找下一级审批:applicationId={}, currentOrder={}",
|
||||||
|
applicationId, currentRecord.getNodeOrder());
|
||||||
|
|
||||||
|
// 查找下一级审批记录(node_order + 1)
|
||||||
|
LambdaQueryWrapper<CwApprovalRecord> nextQuery = Wrappers.<CwApprovalRecord>lambdaQuery()
|
||||||
|
.eq(CwApprovalRecord::getApplicationId, applicationId)
|
||||||
|
.eq(CwApprovalRecord::getNodeOrder, currentRecord.getNodeOrder() + 1);
|
||||||
|
CwApprovalRecord nextRecord = baseMapper.selectOne(nextQuery);
|
||||||
|
|
||||||
|
if (nextRecord != null) {
|
||||||
|
// 存在下一级审批,激活下一级
|
||||||
|
log.info("📋 激活下一级审批:nextRecordId={}, nodeOrder={}, nodeCode={}, currentStatus={}",
|
||||||
|
nextRecord.getId(), nextRecord.getNodeOrder(), nextRecord.getNodeCode(), nextRecord.getNodeStatus());
|
||||||
|
|
||||||
|
// 使用条件更新,确保只有在特定状态下才能激活
|
||||||
|
LambdaUpdateWrapper<CwApprovalRecord> updateWrapper = Wrappers.<CwApprovalRecord>lambdaUpdate()
|
||||||
|
.eq(CwApprovalRecord::getId, nextRecord.getId())
|
||||||
|
.eq(CwApprovalRecord::getApplicationId, applicationId)
|
||||||
|
.ne(CwApprovalRecord::getNodeStatus, "waiting") // 避免重复激活已经是waiting状态的记录
|
||||||
|
.ne(CwApprovalRecord::getNodeStatus, "approved") // 避免激活已完成的记录
|
||||||
|
.ne(CwApprovalRecord::getNodeStatus, "rejected") // 避免激活已拒绝的记录
|
||||||
|
.set(CwApprovalRecord::getNodeStatus, "waiting")
|
||||||
|
.set(CwApprovalRecord::getStartTime, new Date()) // 设置开始时间
|
||||||
|
.set(CwApprovalRecord::getUpdateTime, new Date())
|
||||||
|
.set(CwApprovalRecord::getUpdateBy, LoginHelper.getUserId());
|
||||||
|
|
||||||
|
int updateResult = baseMapper.update(null, updateWrapper);
|
||||||
|
|
||||||
|
if (updateResult <= 0) {
|
||||||
|
log.error("❌ 激活下一级审批失败: applicationId={}, nextRecordId={}, nodeOrder={}, nodeCode={}",
|
||||||
|
applicationId, nextRecord.getId(), nextRecord.getNodeOrder(), nextRecord.getNodeCode());
|
||||||
|
|
||||||
|
// 重新查询记录状态,分析失败原因
|
||||||
|
CwApprovalRecord checkRecord = baseMapper.selectById(nextRecord.getId());
|
||||||
|
if (checkRecord == null) {
|
||||||
|
log.error("❌ 下一级审批记录已不存在: recordId={}", nextRecord.getId());
|
||||||
|
} else {
|
||||||
|
log.error("❌ 下一级审批记录状态不符合激活条件: recordId={}, currentStatus={}, 限制条件=不能是[waiting,approved,rejected]",
|
||||||
|
nextRecord.getId(), checkRecord.getNodeStatus());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("✅ 下一级审批已激活成功: recordId={}, newStatus=waiting", nextRecord.getId());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// 没有下一级审批,整个流程完成
|
||||||
|
log.info("🎉 所有审批完成,更新申报状态为approved: applicationId={}, currentOrder={}",
|
||||||
|
applicationId, currentRecord.getNodeOrder());
|
||||||
|
|
||||||
|
boolean statusUpdateResult = projectApplicationService.updateApprovalStatus(applicationId, "approved");
|
||||||
|
if (!statusUpdateResult) {
|
||||||
|
log.error("❌ 更新申报状态为approved失败: applicationId={}", applicationId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("✅ 申报状态已成功更新为approved: applicationId={}", applicationId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} else if ("reject".equals(approveAction)) {
|
} else if ("reject".equals(approveAction)) {
|
||||||
// 审批拒绝,整个流程结束
|
// 审批拒绝,整个流程结束
|
||||||
return projectApplicationService.updateApprovalStatus(applicationId, "rejected");
|
log.info("❌ 审批拒绝,更新申报状态为rejected: applicationId={}", applicationId);
|
||||||
|
boolean rejectResult = projectApplicationService.updateApprovalStatus(applicationId, "rejected");
|
||||||
|
if (!rejectResult) {
|
||||||
|
log.error("❌ 更新申报状态为rejected失败: applicationId={}", applicationId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log.info("✅ 申报状态已更新为rejected: applicationId={}", applicationId);
|
||||||
|
return true;
|
||||||
|
|
||||||
} else if ("return".equals(approveAction)) {
|
} else if ("return".equals(approveAction)) {
|
||||||
// 退回修改,申报状态改为已退回
|
// 退回修改,申报状态改为已退回,保留历史审批记录
|
||||||
return projectApplicationService.updateApprovalStatus(applicationId, "returned");
|
log.info("↩️ 审批退回,更新申报状态为returned: applicationId={}", applicationId);
|
||||||
|
boolean returnResult = projectApplicationService.updateApprovalStatus(applicationId, "returned");
|
||||||
|
if (!returnResult) {
|
||||||
|
log.error("❌ 更新申报状态为returned失败: applicationId={}", applicationId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log.info("✅ 申报状态已更新为returned: applicationId={}", applicationId);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("❌ 更新申报状态和流程失败:applicationId={}, nodeCode={}, action={}, error={}",
|
||||||
|
applicationId, nodeCode, approveAction, e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean startNextApprovalNode(Long applicationId, String nextNodeCode) {
|
private Boolean startNextApprovalNode(Long applicationId, String nextNodeCode) {
|
||||||
@@ -1450,4 +1546,122 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService {
|
|||||||
throw new RuntimeException("指定院领导审批人失败:" + e.getMessage());
|
throw new RuntimeException("指定院领导审批人失败:" + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建自定义审批流程
|
||||||
|
* 根据用户选择的审批人列表创建自定义审批流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean createCustomApprovalProcess(Long applicationId, String applicationNo,
|
||||||
|
List<Long> auditorIds, Long submitterId, String submitterName) {
|
||||||
|
|
||||||
|
log.info("🚀 创建自定义审批流程:applicationId={}, auditorIds={}", applicationId, auditorIds);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 验证参数
|
||||||
|
if (applicationId == null) {
|
||||||
|
throw new RuntimeException("申报ID不能为空");
|
||||||
|
}
|
||||||
|
if (auditorIds == null || auditorIds.isEmpty()) {
|
||||||
|
throw new RuntimeException("审批人列表不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 验证审批人是否有效
|
||||||
|
List<CwProjectAuditorVo> validAuditors = validateAuditors(auditorIds);
|
||||||
|
if (validAuditors.size() != auditorIds.size()) {
|
||||||
|
throw new RuntimeException("包含无效的审批人,请重新选择");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 创建自定义审批记录
|
||||||
|
List<CwApprovalRecord> records = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < auditorIds.size(); i++) {
|
||||||
|
Long auditorId = auditorIds.get(i);
|
||||||
|
CwProjectAuditorVo auditor = validAuditors.get(i);
|
||||||
|
|
||||||
|
CwApprovalRecord record = new CwApprovalRecord();
|
||||||
|
// 移除手动ID设置,使用MyBatis-Plus自动生成雪花ID(符合项目规范)
|
||||||
|
// record.setId() - 让MyBatis-Plus自动生成,避免主键冲突
|
||||||
|
record.setApplicationId(applicationId);
|
||||||
|
record.setApplicationNo(applicationNo);
|
||||||
|
record.setNodeCode("custom_node_" + (i + 1));
|
||||||
|
record.setNodeName("自定义审批节点" + (i + 1));
|
||||||
|
record.setNodeOrder(i + 1);
|
||||||
|
record.setApproverId(auditorId);
|
||||||
|
record.setApproverName(auditor.getUserName());
|
||||||
|
record.setNodeStatus(i == 0 ? "waiting" : "pending"); // 第一个waiting,其他pending
|
||||||
|
|
||||||
|
// 设置基础字段
|
||||||
|
Date now = new Date();
|
||||||
|
record.setCreateBy(submitterId);
|
||||||
|
record.setCreateTime(now);
|
||||||
|
record.setUpdateBy(submitterId);
|
||||||
|
record.setUpdateTime(now);
|
||||||
|
record.setTenantId(LoginHelper.getTenantId());
|
||||||
|
record.setIsDelegate(false);
|
||||||
|
record.setIsOvertime(false);
|
||||||
|
|
||||||
|
// 🔧 修复:设置startTime字段(数据库NOT NULL约束)
|
||||||
|
if (i == 0) {
|
||||||
|
// 第一个节点:waiting状态,立即开始
|
||||||
|
record.setStartTime(now);
|
||||||
|
log.info("设置自定义审批第一个节点开始时间:{} -> {}", record.getNodeCode(), now);
|
||||||
|
} else {
|
||||||
|
// 后续节点:pending状态,也需要设置startTime满足数据库约束
|
||||||
|
record.setStartTime(now);
|
||||||
|
log.info("设置自定义审批后续节点开始时间:{} -> {} (满足数据库约束)", record.getNodeCode(), now);
|
||||||
|
}
|
||||||
|
|
||||||
|
records.add(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 批量插入审批记录
|
||||||
|
boolean result = false;
|
||||||
|
if (!records.isEmpty()) {
|
||||||
|
for (CwApprovalRecord record : records) {
|
||||||
|
result = baseMapper.insert(record) > 0;
|
||||||
|
if (!result) {
|
||||||
|
throw new RuntimeException("创建审批记录失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 同步更新项目申报状态为"审批中"
|
||||||
|
// 这是关键步骤:启动审批流程的同时提交申报
|
||||||
|
ICwProjectApplicationService applicationService = SpringUtils.getBean(ICwProjectApplicationService.class);
|
||||||
|
boolean statusUpdateResult = applicationService.updateApprovalStatus(applicationId, "approving");
|
||||||
|
if (!statusUpdateResult) {
|
||||||
|
throw new RuntimeException("更新申报状态失败,审批流程创建失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("✅ 自定义审批流程创建成功:共创建{}个审批节点", records.size());
|
||||||
|
log.info("✅ 申报状态已更新为审批中:applicationId={}", applicationId);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("❌ 创建自定义审批流程失败:{}", e.getMessage(), e);
|
||||||
|
throw new RuntimeException("创建审批流程失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证审批人列表
|
||||||
|
*/
|
||||||
|
private List<CwProjectAuditorVo> validateAuditors(List<Long> auditorIds) {
|
||||||
|
List<CwProjectAuditorVo> validAuditors = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Long auditorId : auditorIds) {
|
||||||
|
CwProjectAuditorVo auditor = projectAuditorService.queryByUserId(auditorId);
|
||||||
|
if (auditor == null) {
|
||||||
|
throw new RuntimeException("审批人不存在:" + auditorId);
|
||||||
|
}
|
||||||
|
if (!"1".equals(auditor.getStatus())) {
|
||||||
|
throw new RuntimeException("审批人已停用:" + auditor.getUserName());
|
||||||
|
}
|
||||||
|
validAuditors.add(auditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validAuditors;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+68
-15
@@ -19,8 +19,8 @@ import org.dromara.cw.domain.CwProjectApplication;
|
|||||||
import org.dromara.cw.mapper.CwProjectApplicationMapper;
|
import org.dromara.cw.mapper.CwProjectApplicationMapper;
|
||||||
import org.dromara.cw.service.ICwProjectApplicationService;
|
import org.dromara.cw.service.ICwProjectApplicationService;
|
||||||
import org.dromara.cw.service.ICwPerformanceIndicatorService;
|
import org.dromara.cw.service.ICwPerformanceIndicatorService;
|
||||||
import org.dromara.cw.event.ApprovalProcessCreatedEvent;
|
import org.dromara.cw.service.ICwApprovalRecordService;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -41,7 +41,6 @@ public class CwProjectApplicationServiceImpl implements ICwProjectApplicationSer
|
|||||||
|
|
||||||
private final CwProjectApplicationMapper baseMapper;
|
private final CwProjectApplicationMapper baseMapper;
|
||||||
private final ICwPerformanceIndicatorService performanceIndicatorService;
|
private final ICwPerformanceIndicatorService performanceIndicatorService;
|
||||||
private final ApplicationEventPublisher eventPublisher;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询项目预算申报
|
* 查询项目预算申报
|
||||||
@@ -584,30 +583,34 @@ public class CwProjectApplicationServiceImpl implements ICwProjectApplicationSer
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (updateResult) {
|
if (updateResult) {
|
||||||
// 异步发布审批流程创建事件(避免阻塞主流程)
|
// 直接同步创建审批流程(确保事务一致性)
|
||||||
try {
|
try {
|
||||||
log.info("🚀 准备异步发布审批流程创建事件:applicationId={}, applicationNo={}", id, application.getApplicationNo());
|
log.info("🚀 开始同步创建审批流程:applicationId={}, applicationNo={}", id, application.getApplicationNo());
|
||||||
|
|
||||||
// 在主线程中收集用户上下文信息(异步线程中无法获取)
|
// 收集用户上下文信息
|
||||||
Long currentUserId = LoginHelper.getUserId();
|
Long currentUserId = LoginHelper.getUserId();
|
||||||
String currentUserName = LoginHelper.getUsername();
|
String currentUserName = LoginHelper.getUsername();
|
||||||
Long currentDeptId = LoginHelper.getDeptId();
|
Long currentDeptId = LoginHelper.getDeptId();
|
||||||
String currentTenantId = LoginHelper.getTenantId();
|
String currentTenantId = LoginHelper.getTenantId();
|
||||||
|
|
||||||
log.info("📋 收集用户上下文:userId={}, userName={}, deptId={}, tenantId={}",
|
log.info("📋 用户上下文:userId={}, userName={}, deptId={}, tenantId={}",
|
||||||
currentUserId, currentUserName, currentDeptId, currentTenantId);
|
currentUserId, currentUserName, currentDeptId, currentTenantId);
|
||||||
|
|
||||||
ApprovalProcessCreatedEvent event = new ApprovalProcessCreatedEvent(
|
// 使用SpringUtils动态获取服务,避免循环依赖
|
||||||
this, id, application.getApplicationNo(),
|
ICwApprovalRecordService approvalRecordService = SpringUtils.getBean(ICwApprovalRecordService.class);
|
||||||
|
Boolean createResult = approvalRecordService.createApprovalProcess(
|
||||||
|
id, application.getApplicationNo(),
|
||||||
currentUserId, currentUserName, currentDeptId, currentTenantId);
|
currentUserId, currentUserName, currentDeptId, currentTenantId);
|
||||||
|
|
||||||
log.info("📤 异步发布事件(不阻塞主流程)...");
|
if (createResult) {
|
||||||
eventPublisher.publishEvent(event);
|
log.info("✅ 审批流程创建成功:applicationId={}, applicationNo={}", id, application.getApplicationNo());
|
||||||
log.info("✅ 审批流程创建事件发布完成(异步处理中):applicationId={}, applicationNo={}",
|
} else {
|
||||||
id, application.getApplicationNo());
|
log.error("❌ 审批流程创建失败:applicationId={}, applicationNo={}", id, application.getApplicationNo());
|
||||||
|
throw new RuntimeException("审批流程创建失败");
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("💥 发布审批流程创建事件失败:applicationId={}, error={}", id, e.getMessage(), e);
|
log.error("💥 创建审批流程异常:applicationId={}, error={}", id, e.getMessage(), e);
|
||||||
// 事件发布失败不影响提交,审批流程会在后台异步创建
|
throw new RuntimeException("创建审批流程失败:" + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.error("❌ 申报提交失败,数据库更新返回false:applicationId={}", id);
|
log.error("❌ 申报提交失败,数据库更新返回false:applicationId={}", id);
|
||||||
@@ -667,16 +670,66 @@ public class CwProjectApplicationServiceImpl implements ICwProjectApplicationSer
|
|||||||
throw new RuntimeException("申报记录不存在");
|
throw new RuntimeException("申报记录不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证状态转换的合法性
|
||||||
|
String currentStatus = application.getApprovalStatus();
|
||||||
|
log.info("更新申报状态:applicationId={}, {} -> {}", id, currentStatus, approvalStatus);
|
||||||
|
|
||||||
|
// 当从草稿状态提交到审批中时,进行数据完整性验证和字段设置
|
||||||
|
if ("draft".equals(currentStatus) && "approving".equals(approvalStatus)) {
|
||||||
|
// 验证申报数据完整性
|
||||||
|
validateApplicationForSubmission(application);
|
||||||
|
|
||||||
|
// 设置提交时间(第一次提交)
|
||||||
|
if (application.getSubmitTime() == null) {
|
||||||
|
application.setSubmitTime(new Date());
|
||||||
|
log.info("设置申报提交时间:applicationId={}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成申报编号(如果还没有)
|
||||||
|
if (StringUtils.isBlank(application.getApplicationNo())) {
|
||||||
|
String applicationNo = generateApplicationNo();
|
||||||
|
application.setApplicationNo(applicationNo);
|
||||||
|
log.info("生成申报编号:applicationId={}, applicationNo={}", id, applicationNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("✅ 申报正式提交:从草稿状态提交到审批中");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新审批状态
|
||||||
application.setApprovalStatus(approvalStatus);
|
application.setApprovalStatus(approvalStatus);
|
||||||
|
|
||||||
// 如果是最终通过或拒绝状态,设置审批完成时间
|
// 如果是最终通过或拒绝状态,设置审批完成时间
|
||||||
if ("approved".equals(approvalStatus) || "rejected".equals(approvalStatus)) {
|
if ("approved".equals(approvalStatus) || "rejected".equals(approvalStatus)) {
|
||||||
application.setApproveTime(new Date());
|
application.setApproveTime(new Date());
|
||||||
|
log.info("设置审批完成时间:applicationId={}, status={}", id, approvalStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseMapper.updateById(application) > 0;
|
return baseMapper.updateById(application) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证申报数据完整性(提交时验证)
|
||||||
|
*/
|
||||||
|
private void validateApplicationForSubmission(CwProjectApplication application) {
|
||||||
|
if (StringUtils.isBlank(application.getProjectName())) {
|
||||||
|
throw new RuntimeException("项目名称不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(application.getPrincipal())) {
|
||||||
|
throw new RuntimeException("项目负责人不能为空");
|
||||||
|
}
|
||||||
|
if (application.getTotalInvestment() == null || application.getTotalInvestment().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new RuntimeException("项目总投资必须大于0");
|
||||||
|
}
|
||||||
|
if (application.getYear() == null) {
|
||||||
|
throw new RuntimeException("预算年度不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(application.getApplicationReason())) {
|
||||||
|
throw new RuntimeException("项目申请原因不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("✅ 申报数据完整性验证通过:applicationId={}", application.getId());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成申报编号
|
* 生成申报编号
|
||||||
*/
|
*/
|
||||||
|
|||||||
+289
@@ -0,0 +1,289 @@
|
|||||||
|
package org.dromara.cw.service.impl;
|
||||||
|
|
||||||
|
import org.dromara.common.core.utils.MapstructUtils;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.dromara.cw.domain.bo.CwProjectApprovalProcessBo;
|
||||||
|
import org.dromara.cw.domain.bo.CwCustomApprovalBo;
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo;
|
||||||
|
import org.dromara.cw.domain.CwProjectApprovalProcess;
|
||||||
|
import org.dromara.cw.mapper.CwProjectApprovalProcessMapper;
|
||||||
|
import org.dromara.cw.service.ICwProjectApprovalProcessService;
|
||||||
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程Service业务层处理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class CwProjectApprovalProcessServiceImpl implements ICwProjectApprovalProcessService {
|
||||||
|
|
||||||
|
private final CwProjectApprovalProcessMapper baseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审批流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CwProjectApprovalProcessVo queryById(Long id){
|
||||||
|
return baseMapper.selectVoById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审批流程列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<CwProjectApprovalProcessVo> queryPageList(CwProjectApprovalProcessVo vo, PageQuery pageQuery) {
|
||||||
|
LambdaQueryWrapper<CwProjectApprovalProcess> lqw = buildQueryWrapper(vo);
|
||||||
|
Page<CwProjectApprovalProcessVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||||
|
return TableDataInfo.build(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审批流程列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CwProjectApprovalProcessVo> queryList(CwProjectApprovalProcessVo vo) {
|
||||||
|
LambdaQueryWrapper<CwProjectApprovalProcess> lqw = buildQueryWrapper(vo);
|
||||||
|
return baseMapper.selectVoList(lqw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LambdaQueryWrapper<CwProjectApprovalProcess> buildQueryWrapper(CwProjectApprovalProcessVo vo) {
|
||||||
|
LambdaQueryWrapper<CwProjectApprovalProcess> lqw = Wrappers.lambdaQuery();
|
||||||
|
lqw.eq(vo.getApplicationId() != null, CwProjectApprovalProcess::getApplicationId, vo.getApplicationId());
|
||||||
|
lqw.like(StringUtils.isNotBlank(vo.getApplicationNo()), CwProjectApprovalProcess::getApplicationNo, vo.getApplicationNo());
|
||||||
|
lqw.like(StringUtils.isNotBlank(vo.getProcessName()), CwProjectApprovalProcess::getProcessName, vo.getProcessName());
|
||||||
|
lqw.eq(StringUtils.isNotBlank(vo.getProcessStatus()), CwProjectApprovalProcess::getProcessStatus, vo.getProcessStatus());
|
||||||
|
lqw.eq(vo.getTotalNodes() != null, CwProjectApprovalProcess::getTotalNodes, vo.getTotalNodes());
|
||||||
|
lqw.eq(vo.getCurrentNode() != null, CwProjectApprovalProcess::getCurrentNode, vo.getCurrentNode());
|
||||||
|
lqw.eq(vo.getSubmitterId() != null, CwProjectApprovalProcess::getSubmitterId, vo.getSubmitterId());
|
||||||
|
lqw.like(StringUtils.isNotBlank(vo.getSubmitterName()), CwProjectApprovalProcess::getSubmitterName, vo.getSubmitterName());
|
||||||
|
return lqw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存前的数据校验
|
||||||
|
*/
|
||||||
|
private void validEntityBeforeSave(CwProjectApprovalProcess entity){
|
||||||
|
// 检查申报ID是否已存在流程
|
||||||
|
if (entity.getApplicationId() != null) {
|
||||||
|
LambdaQueryWrapper<CwProjectApprovalProcess> wrapper = Wrappers.lambdaQuery();
|
||||||
|
wrapper.eq(CwProjectApprovalProcess::getApplicationId, entity.getApplicationId());
|
||||||
|
if (entity.getId() != null) {
|
||||||
|
wrapper.ne(CwProjectApprovalProcess::getId, entity.getId());
|
||||||
|
}
|
||||||
|
if (baseMapper.exists(wrapper)) {
|
||||||
|
throw new RuntimeException("该申报已存在审批流程");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除项目审批流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||||
|
if(isValid){
|
||||||
|
//TODO 做一些业务上的校验,判断是否需要校验
|
||||||
|
}
|
||||||
|
return baseMapper.deleteBatchIds(ids) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据申报ID查询审批流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CwProjectApprovalProcessVo queryByApplicationId(Long applicationId) {
|
||||||
|
LambdaQueryWrapper<CwProjectApprovalProcess> wrapper = Wrappers.lambdaQuery();
|
||||||
|
wrapper.eq(CwProjectApprovalProcess::getApplicationId, applicationId);
|
||||||
|
return baseMapper.selectVoOne(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据申报编号查询审批流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CwProjectApprovalProcessVo queryByApplicationNo(String applicationNo) {
|
||||||
|
LambdaQueryWrapper<CwProjectApprovalProcess> wrapper = Wrappers.lambdaQuery();
|
||||||
|
wrapper.eq(CwProjectApprovalProcess::getApplicationNo, applicationNo);
|
||||||
|
return baseMapper.selectVoOne(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询我提交的流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CwProjectApprovalProcessVo> queryMyProcesses(Long submitterId) {
|
||||||
|
LambdaQueryWrapper<CwProjectApprovalProcess> wrapper = Wrappers.lambdaQuery();
|
||||||
|
wrapper.eq(CwProjectApprovalProcess::getSubmitterId, submitterId);
|
||||||
|
wrapper.orderByDesc(CwProjectApprovalProcess::getCreateTime);
|
||||||
|
return baseMapper.selectVoList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询运行中的流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CwProjectApprovalProcessVo> queryRunningProcesses() {
|
||||||
|
LambdaQueryWrapper<CwProjectApprovalProcess> wrapper = Wrappers.lambdaQuery();
|
||||||
|
wrapper.eq(CwProjectApprovalProcess::getProcessStatus, "running");
|
||||||
|
wrapper.orderByDesc(CwProjectApprovalProcess::getCreateTime);
|
||||||
|
return baseMapper.selectVoList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询超时流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CwProjectApprovalProcessVo> queryOvertimeProcesses(Integer hours) {
|
||||||
|
// 这里可以根据需要实现超时查询逻辑
|
||||||
|
LambdaQueryWrapper<CwProjectApprovalProcess> wrapper = Wrappers.lambdaQuery();
|
||||||
|
wrapper.eq(CwProjectApprovalProcess::getProcessStatus, "running");
|
||||||
|
wrapper.orderByDesc(CwProjectApprovalProcess::getCreateTime);
|
||||||
|
return baseMapper.selectVoList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动自定义审批流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Boolean startCustomApprovalProcess(CwCustomApprovalBo bo) {
|
||||||
|
try {
|
||||||
|
// 1. 创建审批流程
|
||||||
|
CwProjectApprovalProcess process = new CwProjectApprovalProcess();
|
||||||
|
process.setApplicationId(bo.getApplicationId());
|
||||||
|
process.setApplicationNo(bo.getApplicationNo());
|
||||||
|
process.setProcessName(bo.getProcessName());
|
||||||
|
process.setProcessStatus("running");
|
||||||
|
process.setTotalNodes(bo.getAuditorIds().size());
|
||||||
|
process.setCurrentNode(1);
|
||||||
|
process.setStartTime(new Date());
|
||||||
|
process.setSubmitterId(bo.getSubmitterId());
|
||||||
|
process.setSubmitterName(bo.getSubmitterName());
|
||||||
|
|
||||||
|
boolean result = baseMapper.insert(process) > 0;
|
||||||
|
|
||||||
|
// TODO: 这里可以创建对应的审批记录
|
||||||
|
// 由于CwApprovalRecord相关的实现比较复杂,这里先简化处理
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("启动审批流程失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终止审批流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Boolean terminateProcess(Long id, String reason) {
|
||||||
|
CwProjectApprovalProcess update = new CwProjectApprovalProcess();
|
||||||
|
update.setId(id);
|
||||||
|
update.setProcessStatus("terminated");
|
||||||
|
update.setEndTime(new Date());
|
||||||
|
update.setUpdateBy(LoginHelper.getUserId());
|
||||||
|
return baseMapper.updateById(update) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推进流程到下一节点
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean advanceToNextNode(Long processId) {
|
||||||
|
CwProjectApprovalProcess process = baseMapper.selectById(processId);
|
||||||
|
if (process != null && process.getCurrentNode() < process.getTotalNodes()) {
|
||||||
|
return updateCurrentNode(processId, process.getCurrentNode() + 1);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成审批流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean completeProcess(Long processId) {
|
||||||
|
return updateProcessStatus(processId, "completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新流程当前节点
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean updateCurrentNode(Long id, Integer currentNode) {
|
||||||
|
CwProjectApprovalProcess update = new CwProjectApprovalProcess();
|
||||||
|
update.setId(id);
|
||||||
|
update.setCurrentNode(currentNode);
|
||||||
|
update.setUpdateBy(LoginHelper.getUserId());
|
||||||
|
return baseMapper.updateById(update) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新流程状态
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean updateProcessStatus(Long id, String status) {
|
||||||
|
CwProjectApprovalProcess update = new CwProjectApprovalProcess();
|
||||||
|
update.setId(id);
|
||||||
|
update.setProcessStatus(status);
|
||||||
|
update.setUpdateBy(LoginHelper.getUserId());
|
||||||
|
if ("completed".equals(status) || "terminated".equals(status)) {
|
||||||
|
update.setEndTime(new Date());
|
||||||
|
}
|
||||||
|
return baseMapper.updateById(update) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证审批流程配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean validateApprovalProcess(CwCustomApprovalBo bo) {
|
||||||
|
// 验证基本参数
|
||||||
|
if (bo.getApplicationId() == null ||
|
||||||
|
StringUtils.isBlank(bo.getApplicationNo()) ||
|
||||||
|
bo.getAuditorIds() == null || bo.getAuditorIds().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO: 可以添加更多验证逻辑
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计审批流程状态
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Map<String, Object>> queryProcessStatistics(Long submitterId) {
|
||||||
|
// 这里可以根据需要实现统计逻辑
|
||||||
|
return List.of(Map.of(
|
||||||
|
"totalProcesses", 0,
|
||||||
|
"runningProcesses", 0,
|
||||||
|
"completedProcesses", 0,
|
||||||
|
"terminatedProcesses", 0
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出数据
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CwProjectApprovalProcessVo> exportList(CwProjectApprovalProcessVo vo) {
|
||||||
|
return queryList(vo);
|
||||||
|
}
|
||||||
|
}
|
||||||
+258
@@ -0,0 +1,258 @@
|
|||||||
|
package org.dromara.cw.service.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.dromara.cw.domain.bo.CwCustomApprovalBo;
|
||||||
|
import org.dromara.cw.domain.bo.CwApprovalActionBo;
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo;
|
||||||
|
import org.dromara.cw.domain.vo.CwApprovalRecordVo;
|
||||||
|
import org.dromara.cw.service.ICwProjectApprovalService;
|
||||||
|
import org.dromara.cw.service.ICwProjectApprovalProcessService;
|
||||||
|
import org.dromara.cw.service.ICwApprovalRecordService;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import org.dromara.cw.domain.CwApprovalRecord;
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程Service业务层处理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class CwProjectApprovalServiceImpl implements ICwProjectApprovalService {
|
||||||
|
|
||||||
|
private final ICwProjectApprovalProcessService processService;
|
||||||
|
private final ICwApprovalRecordService recordService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动自定义审批流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean startCustomApprovalProcess(CwCustomApprovalBo bo) {
|
||||||
|
try {
|
||||||
|
log.info("🚀 启动自定义审批流程: applicationId={}, auditorIds={}",
|
||||||
|
bo.getApplicationId(), bo.getAuditorIds());
|
||||||
|
|
||||||
|
// 1. 验证参数
|
||||||
|
if (bo.getApplicationId() == null) {
|
||||||
|
throw new RuntimeException("申报ID不能为空");
|
||||||
|
}
|
||||||
|
if (bo.getAuditorIds() == null || bo.getAuditorIds().isEmpty()) {
|
||||||
|
throw new RuntimeException("审批人列表不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 调用审批记录服务创建自定义审批流程
|
||||||
|
Boolean result = recordService.createCustomApprovalProcess(
|
||||||
|
bo.getApplicationId(),
|
||||||
|
bo.getApplicationNo(),
|
||||||
|
bo.getAuditorIds(),
|
||||||
|
bo.getSubmitterId(),
|
||||||
|
bo.getSubmitterName()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
log.info("✅ 自定义审批流程启动成功: applicationId={}", bo.getApplicationId());
|
||||||
|
} else {
|
||||||
|
log.error("❌ 自定义审批流程启动失败: applicationId={}", bo.getApplicationId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("❌ 启动自定义审批流程失败: applicationId={}, error={}",
|
||||||
|
bo.getApplicationId(), e.getMessage(), e);
|
||||||
|
throw new RuntimeException("启动审批流程失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行审批操作
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean executeApproval(CwApprovalActionBo bo) {
|
||||||
|
try {
|
||||||
|
log.info("执行审批操作: recordId={}, action={}, opinion={}",
|
||||||
|
bo.getRecordId(), bo.getAction(), bo.getOpinion());
|
||||||
|
|
||||||
|
// 🔧 修复:先通过recordId获取审批记录信息
|
||||||
|
CwApprovalRecordVo record = recordService.queryById(bo.getRecordId());
|
||||||
|
if (record == null) {
|
||||||
|
throw new RuntimeException("审批记录不存在:" + bo.getRecordId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔧 修复:调用现有的审批记录服务执行审批
|
||||||
|
Boolean result = recordService.processApproval(
|
||||||
|
record.getApplicationId(),
|
||||||
|
record.getNodeCode(),
|
||||||
|
bo.getAction(),
|
||||||
|
bo.getOpinion()
|
||||||
|
);
|
||||||
|
|
||||||
|
log.info("✅ 审批操作执行完成:recordId={}, action={}, result={}",
|
||||||
|
bo.getRecordId(), bo.getAction(), result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("执行审批操作失败: recordId={}, action={}, error={}",
|
||||||
|
bo.getRecordId(), bo.getAction(), e.getMessage(), e);
|
||||||
|
throw new RuntimeException("审批操作失败:" + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的待审批列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<CwApprovalRecordVo> getMyPendingApprovals(PageQuery pageQuery) {
|
||||||
|
try {
|
||||||
|
Long userId = LoginHelper.getUserId();
|
||||||
|
log.info("获取用户{}的待审批列表", userId);
|
||||||
|
|
||||||
|
// 🔧 修复:调用现有的审批记录服务查询方法
|
||||||
|
List<CwApprovalRecordVo> list = recordService.queryPendingByApproverId(userId);
|
||||||
|
|
||||||
|
// 应用分页
|
||||||
|
int total = list.size();
|
||||||
|
int start = Math.max(0, (pageQuery.getPageNum() - 1) * pageQuery.getPageSize());
|
||||||
|
int end = Math.min(start + pageQuery.getPageSize(), total);
|
||||||
|
|
||||||
|
List<CwApprovalRecordVo> pagedList = start < total ?
|
||||||
|
list.subList(start, end) : new ArrayList<>();
|
||||||
|
|
||||||
|
log.info("✅ 查询待审批列表成功:总数={}, 当前页={}, 返回数量={}",
|
||||||
|
total, pageQuery.getPageNum(), pagedList.size());
|
||||||
|
|
||||||
|
// 🔧 修复:使用正确的TableDataInfo构造方法
|
||||||
|
TableDataInfo<CwApprovalRecordVo> result = new TableDataInfo<>(pagedList, total);
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取待审批列表失败: {}", e.getMessage(), e);
|
||||||
|
return TableDataInfo.build(new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的已审批列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<CwApprovalRecordVo> getMyProcessedApprovals(PageQuery pageQuery) {
|
||||||
|
try {
|
||||||
|
Long userId = LoginHelper.getUserId();
|
||||||
|
log.info("获取用户{}的已审批列表", userId);
|
||||||
|
|
||||||
|
// 🔧 修复:直接查询已审批记录,然后手动分页
|
||||||
|
List<CwApprovalRecordVo> allList = recordService.queryApprovedByCurrentUser();
|
||||||
|
|
||||||
|
// 应用分页
|
||||||
|
int total = allList.size();
|
||||||
|
int start = Math.max(0, (pageQuery.getPageNum() - 1) * pageQuery.getPageSize());
|
||||||
|
int end = Math.min(start + pageQuery.getPageSize(), total);
|
||||||
|
|
||||||
|
List<CwApprovalRecordVo> pagedList = start < total ?
|
||||||
|
allList.subList(start, end) : new ArrayList<>();
|
||||||
|
|
||||||
|
log.info("✅ 查询已审批列表成功:总数={}, 当前页={}, 返回数量={}",
|
||||||
|
total, pageQuery.getPageNum(), pagedList.size());
|
||||||
|
|
||||||
|
// 🔧 修复:使用正确的TableDataInfo构造方法
|
||||||
|
TableDataInfo<CwApprovalRecordVo> result = new TableDataInfo<>(pagedList, total);
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取已审批列表失败: {}", e.getMessage(), e);
|
||||||
|
return TableDataInfo.build(new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询审批流程列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<CwProjectApprovalProcessVo> queryProcessList(PageQuery pageQuery) {
|
||||||
|
try {
|
||||||
|
log.info("查询审批流程列表");
|
||||||
|
|
||||||
|
// 这里应该查询具体的流程数据
|
||||||
|
// 由于缺少具体的业务逻辑,这里返回空列表
|
||||||
|
// 实际项目中需要根据具体业务需求实现
|
||||||
|
|
||||||
|
List<CwProjectApprovalProcessVo> list = new ArrayList<>();
|
||||||
|
return TableDataInfo.build(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询审批流程列表失败: {}", e.getMessage(), e);
|
||||||
|
return TableDataInfo.build(new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审批流程详情
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CwProjectApprovalProcessVo getProcessDetail(Long processId) {
|
||||||
|
try {
|
||||||
|
log.info("获取审批流程详情: {}", processId);
|
||||||
|
|
||||||
|
// 这里应该查询具体的流程详情
|
||||||
|
// 由于缺少具体的业务逻辑,这里返回null
|
||||||
|
// 实际项目中需要根据具体业务需求实现
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取审批流程详情失败: {}", e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审批记录列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CwApprovalRecordVo> getApprovalRecords(Long applicationId) {
|
||||||
|
try {
|
||||||
|
log.info("获取审批记录列表: {}", applicationId);
|
||||||
|
|
||||||
|
// 这里应该查询具体的审批记录
|
||||||
|
// 由于缺少具体的业务逻辑,这里返回空列表
|
||||||
|
// 实际项目中需要根据具体业务需求实现
|
||||||
|
|
||||||
|
return new ArrayList<>();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取审批记录列表失败: {}", e.getMessage(), e);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终止审批流程
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean terminateProcess(List<Long> processIds) {
|
||||||
|
try {
|
||||||
|
log.info("终止审批流程: {}", processIds);
|
||||||
|
|
||||||
|
// 这里应该实现具体的流程终止逻辑
|
||||||
|
// 由于缺少具体的业务逻辑,这里返回成功状态
|
||||||
|
// 实际项目中需要根据具体业务需求实现
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("终止审批流程失败: {}", e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+343
@@ -0,0 +1,343 @@
|
|||||||
|
package org.dromara.cw.service.impl;
|
||||||
|
|
||||||
|
import org.dromara.common.core.utils.MapstructUtils;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.dromara.cw.domain.bo.CwProjectAuditorBo;
|
||||||
|
import org.dromara.cw.domain.vo.CwProjectAuditorVo;
|
||||||
|
import org.dromara.cw.domain.CwProjectAuditor;
|
||||||
|
import org.dromara.cw.mapper.CwProjectAuditorMapper;
|
||||||
|
import org.dromara.cw.service.ICwProjectAuditorService;
|
||||||
|
import org.dromara.system.service.ISysUserService;
|
||||||
|
import org.dromara.system.domain.vo.SysUserVo;
|
||||||
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.temporal.TemporalAdjusters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审核人配置Service业务层处理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2025-01-03
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class CwProjectAuditorServiceImpl implements ICwProjectAuditorService {
|
||||||
|
|
||||||
|
private final CwProjectAuditorMapper baseMapper;
|
||||||
|
private final ISysUserService sysUserService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审核人配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CwProjectAuditorVo queryById(Long id){
|
||||||
|
return baseMapper.selectVoById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审核人配置列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<CwProjectAuditorVo> queryPageList(CwProjectAuditorBo bo, PageQuery pageQuery) {
|
||||||
|
LambdaQueryWrapper<CwProjectAuditor> lqw = buildQueryWrapper(bo);
|
||||||
|
Page<CwProjectAuditorVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||||
|
return TableDataInfo.build(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审核人配置列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CwProjectAuditorVo> queryList(CwProjectAuditorBo bo) {
|
||||||
|
LambdaQueryWrapper<CwProjectAuditor> lqw = buildQueryWrapper(bo);
|
||||||
|
return baseMapper.selectVoList(lqw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LambdaQueryWrapper<CwProjectAuditor> buildQueryWrapper(CwProjectAuditorBo bo) {
|
||||||
|
Map<String, Object> params = bo.getParams();
|
||||||
|
LambdaQueryWrapper<CwProjectAuditor> lqw = Wrappers.lambdaQuery();
|
||||||
|
lqw.eq(bo.getUserId() != null, CwProjectAuditor::getUserId, bo.getUserId());
|
||||||
|
lqw.like(StringUtils.isNotBlank(bo.getUserName()), CwProjectAuditor::getUserName, bo.getUserName());
|
||||||
|
lqw.like(StringUtils.isNotBlank(bo.getNickName()), CwProjectAuditor::getNickName, bo.getNickName());
|
||||||
|
lqw.eq(bo.getDeptId() != null, CwProjectAuditor::getDeptId, bo.getDeptId());
|
||||||
|
lqw.like(StringUtils.isNotBlank(bo.getDeptName()), CwProjectAuditor::getDeptName, bo.getDeptName());
|
||||||
|
lqw.eq(StringUtils.isNotBlank(bo.getPhoneNumber()), CwProjectAuditor::getPhoneNumber, bo.getPhoneNumber());
|
||||||
|
lqw.eq(StringUtils.isNotBlank(bo.getEmail()), CwProjectAuditor::getEmail, bo.getEmail());
|
||||||
|
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), CwProjectAuditor::getStatus, bo.getStatus());
|
||||||
|
lqw.eq(bo.getConfigBy() != null, CwProjectAuditor::getConfigBy, bo.getConfigBy());
|
||||||
|
lqw.like(StringUtils.isNotBlank(bo.getConfigByName()), CwProjectAuditor::getConfigByName, bo.getConfigByName());
|
||||||
|
return lqw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增项目审核人配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean insertByBo(CwProjectAuditorBo bo) {
|
||||||
|
CwProjectAuditor add = MapstructUtils.convert(bo, CwProjectAuditor.class);
|
||||||
|
validEntityBeforeSave(add);
|
||||||
|
boolean flag = baseMapper.insert(add) > 0;
|
||||||
|
if (flag) {
|
||||||
|
bo.setId(add.getId());
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改项目审核人配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean updateByBo(CwProjectAuditorBo bo) {
|
||||||
|
CwProjectAuditor update = MapstructUtils.convert(bo, CwProjectAuditor.class);
|
||||||
|
validEntityBeforeSave(update);
|
||||||
|
return baseMapper.updateById(update) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存前的数据校验
|
||||||
|
*/
|
||||||
|
private void validEntityBeforeSave(CwProjectAuditor entity){
|
||||||
|
// 检查用户是否已存在
|
||||||
|
if (entity.getUserId() != null) {
|
||||||
|
LambdaQueryWrapper<CwProjectAuditor> wrapper = Wrappers.lambdaQuery();
|
||||||
|
wrapper.eq(CwProjectAuditor::getUserId, entity.getUserId());
|
||||||
|
if (entity.getId() != null) {
|
||||||
|
wrapper.ne(CwProjectAuditor::getId, entity.getId());
|
||||||
|
}
|
||||||
|
if (baseMapper.exists(wrapper)) {
|
||||||
|
throw new RuntimeException("该用户已经是审核人,不能重复添加");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除项目审核人配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||||
|
if(isValid){
|
||||||
|
//TODO 做一些业务上的校验,判断是否需要校验
|
||||||
|
}
|
||||||
|
return baseMapper.deleteBatchIds(ids) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加审核人
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Boolean batchAddAuditors(List<Long> userIds, Long configBy, String configByName, String remark) {
|
||||||
|
if (userIds == null || userIds.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 获取用户信息
|
||||||
|
List<SysUserVo> users = sysUserService.selectUserByIds(userIds, null);
|
||||||
|
if (users.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 过滤已存在的审核人
|
||||||
|
List<Long> existingUserIds = baseMapper.selectList(Wrappers.<CwProjectAuditor>lambdaQuery()
|
||||||
|
.in(CwProjectAuditor::getUserId, userIds))
|
||||||
|
.stream().map(CwProjectAuditor::getUserId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 3. 构建新增审核人列表
|
||||||
|
List<CwProjectAuditor> auditors = new ArrayList<>();
|
||||||
|
for (SysUserVo user : users) {
|
||||||
|
if (!existingUserIds.contains(user.getUserId())) {
|
||||||
|
CwProjectAuditor auditor = new CwProjectAuditor();
|
||||||
|
auditor.setUserId(user.getUserId());
|
||||||
|
auditor.setUserName(user.getUserName());
|
||||||
|
auditor.setNickName(user.getNickName());
|
||||||
|
auditor.setDeptId(user.getDeptId());
|
||||||
|
auditor.setDeptName(user.getDeptName() != null ? user.getDeptName() : "");
|
||||||
|
auditor.setPhoneNumber(user.getPhonenumber());
|
||||||
|
auditor.setEmail(user.getEmail());
|
||||||
|
auditor.setStatus("1"); // 默认正常状态
|
||||||
|
auditor.setConfigBy(configBy);
|
||||||
|
auditor.setConfigByName(configByName);
|
||||||
|
auditor.setRemark(remark);
|
||||||
|
auditors.add(auditor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 批量保存
|
||||||
|
if (!auditors.isEmpty()) {
|
||||||
|
return baseMapper.insertBatch(auditors);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取可用的审核人列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<CwProjectAuditorVo> getAvailableAuditors() {
|
||||||
|
LambdaQueryWrapper<CwProjectAuditor> wrapper = Wrappers.lambdaQuery();
|
||||||
|
wrapper.eq(CwProjectAuditor::getStatus, "1"); // 只查询正常状态的审核人
|
||||||
|
wrapper.orderByAsc(CwProjectAuditor::getDeptId, CwProjectAuditor::getUserName);
|
||||||
|
return baseMapper.selectVoList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据部门ID获取用户列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Object> getDeptUsers(Long deptId) {
|
||||||
|
try {
|
||||||
|
// 参数验证
|
||||||
|
if (deptId == null || deptId <= 0) {
|
||||||
|
log.warn("无效的部门ID: {}", deptId);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用系统用户服务获取部门用户
|
||||||
|
List<SysUserVo> users = sysUserService.selectUserListByDept(deptId);
|
||||||
|
if (users == null || users.isEmpty()) {
|
||||||
|
log.info("部门 {} 下没有找到用户", deptId);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return users.stream().map(user -> {
|
||||||
|
Map<String, Object> userMap = Map.of(
|
||||||
|
"userId", user.getUserId(),
|
||||||
|
"userName", user.getUserName(),
|
||||||
|
"nickName", user.getNickName() != null ? user.getNickName() : user.getUserName(),
|
||||||
|
"deptId", user.getDeptId(),
|
||||||
|
"deptName", user.getDeptName() != null ? user.getDeptName() : "",
|
||||||
|
"phonenumber", user.getPhonenumber() != null ? user.getPhonenumber() : "",
|
||||||
|
"email", user.getEmail() != null ? user.getEmail() : ""
|
||||||
|
);
|
||||||
|
return (Object) userMap;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取部门 {} 的用户列表失败: {}", deptId, e.getMessage(), e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新审核人状态
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean updateStatus(List<Long> ids, String status) {
|
||||||
|
if (ids == null || ids.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaQueryWrapper<CwProjectAuditor> wrapper = Wrappers.lambdaQuery();
|
||||||
|
wrapper.in(CwProjectAuditor::getId, ids);
|
||||||
|
|
||||||
|
CwProjectAuditor update = new CwProjectAuditor();
|
||||||
|
update.setStatus(status);
|
||||||
|
update.setUpdateBy(LoginHelper.getUserId());
|
||||||
|
|
||||||
|
return baseMapper.update(update, wrapper) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getStatistics() {
|
||||||
|
// 获取总审核人数
|
||||||
|
long totalAuditors = baseMapper.selectCount(Wrappers.<CwProjectAuditor>lambdaQuery()
|
||||||
|
.eq(CwProjectAuditor::getDeleted, 0));
|
||||||
|
|
||||||
|
// 获取正常状态审核人数
|
||||||
|
long activeAuditors = baseMapper.selectCount(Wrappers.<CwProjectAuditor>lambdaQuery()
|
||||||
|
.eq(CwProjectAuditor::getDeleted, 0)
|
||||||
|
.eq(CwProjectAuditor::getStatus, "1"));
|
||||||
|
|
||||||
|
// 获取停用状态审核人数
|
||||||
|
long inactiveAuditors = baseMapper.selectCount(Wrappers.<CwProjectAuditor>lambdaQuery()
|
||||||
|
.eq(CwProjectAuditor::getDeleted, 0)
|
||||||
|
.eq(CwProjectAuditor::getStatus, "0"));
|
||||||
|
|
||||||
|
// 获取部门数量(去重)
|
||||||
|
List<CwProjectAuditor> auditors = baseMapper.selectList(Wrappers.<CwProjectAuditor>lambdaQuery()
|
||||||
|
.eq(CwProjectAuditor::getDeleted, 0)
|
||||||
|
.select(CwProjectAuditor::getDeptId));
|
||||||
|
long totalDepts = auditors.stream().map(CwProjectAuditor::getDeptId).distinct().count();
|
||||||
|
|
||||||
|
// 获取今日新增
|
||||||
|
LocalDateTime startOfDay = LocalDateTime.now().with(LocalTime.MIN);
|
||||||
|
LocalDateTime endOfDay = LocalDateTime.now().with(LocalTime.MAX);
|
||||||
|
long todayAdded = baseMapper.selectCount(Wrappers.<CwProjectAuditor>lambdaQuery()
|
||||||
|
.eq(CwProjectAuditor::getDeleted, 0)
|
||||||
|
.ge(CwProjectAuditor::getCreateTime, startOfDay)
|
||||||
|
.le(CwProjectAuditor::getCreateTime, endOfDay));
|
||||||
|
|
||||||
|
// 获取本月新增
|
||||||
|
LocalDateTime startOfMonth = LocalDateTime.now().with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
|
||||||
|
LocalDateTime endOfMonth = LocalDateTime.now().with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
|
||||||
|
long monthlyAdded = baseMapper.selectCount(Wrappers.<CwProjectAuditor>lambdaQuery()
|
||||||
|
.eq(CwProjectAuditor::getDeleted, 0)
|
||||||
|
.ge(CwProjectAuditor::getCreateTime, startOfMonth)
|
||||||
|
.le(CwProjectAuditor::getCreateTime, endOfMonth));
|
||||||
|
|
||||||
|
// 获取我配置的审核人数
|
||||||
|
long myConfigured = baseMapper.selectCount(Wrappers.<CwProjectAuditor>lambdaQuery()
|
||||||
|
.eq(CwProjectAuditor::getDeleted, 0)
|
||||||
|
.eq(CwProjectAuditor::getConfigBy, LoginHelper.getUserId()));
|
||||||
|
|
||||||
|
// 获取最近活跃数(近7天有更新的)
|
||||||
|
LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays(7);
|
||||||
|
long recentActive = baseMapper.selectCount(Wrappers.<CwProjectAuditor>lambdaQuery()
|
||||||
|
.eq(CwProjectAuditor::getDeleted, 0)
|
||||||
|
.ge(CwProjectAuditor::getUpdateTime, sevenDaysAgo));
|
||||||
|
|
||||||
|
// 构建统计结果
|
||||||
|
Map<String, Object> statistics = new HashMap<>();
|
||||||
|
statistics.put("totalAuditors", totalAuditors);
|
||||||
|
statistics.put("activeAuditors", activeAuditors);
|
||||||
|
statistics.put("inactiveAuditors", inactiveAuditors);
|
||||||
|
statistics.put("totalDepts", totalDepts);
|
||||||
|
statistics.put("todayAdded", todayAdded);
|
||||||
|
statistics.put("monthlyAdded", monthlyAdded);
|
||||||
|
statistics.put("myConfigured", myConfigured);
|
||||||
|
statistics.put("recentActive", recentActive);
|
||||||
|
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID查询项目审核人配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CwProjectAuditorVo queryByUserId(Long userId) {
|
||||||
|
if (userId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
LambdaQueryWrapper<CwProjectAuditor> wrapper = Wrappers.<CwProjectAuditor>lambdaQuery()
|
||||||
|
.eq(CwProjectAuditor::getUserId, userId)
|
||||||
|
.eq(CwProjectAuditor::getDeleted, 0)
|
||||||
|
.orderByDesc(CwProjectAuditor::getCreateTime)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
|
||||||
|
CwProjectAuditor entity = baseMapper.selectOne(wrapper);
|
||||||
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MapstructUtils.convert(entity, CwProjectAuditorVo.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-1
@@ -72,6 +72,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="approvalStatus" column="approval_status" />
|
<result property="approvalStatus" column="approval_status" />
|
||||||
<result property="submitTime" column="submit_time" />
|
<result property="submitTime" column="submit_time" />
|
||||||
<result property="approvalTime" column="approval_time" />
|
<result property="approvalTime" column="approval_time" />
|
||||||
|
<!-- 流程相关字段 -->
|
||||||
|
<result property="totalNodes" column="total_nodes" />
|
||||||
|
<result property="currentNode" column="current_node" />
|
||||||
|
<result property="processName" column="process_name" />
|
||||||
|
<result property="processStatus" column="process_status" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectCwApprovalRecordVo">
|
<sql id="selectCwApprovalRecordVo">
|
||||||
@@ -90,7 +95,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
ar.approver_name as current_approver_name,
|
ar.approver_name as current_approver_name,
|
||||||
ar.node_status as approval_status,
|
ar.node_status as approval_status,
|
||||||
pa.create_time as submit_time,
|
pa.create_time as submit_time,
|
||||||
ar.end_time as approval_time
|
ar.end_time as approval_time,
|
||||||
|
-- 流程相关字段(通过审批记录计算)
|
||||||
|
(SELECT COUNT(DISTINCT ar2.node_order)
|
||||||
|
FROM cw_approval_record ar2
|
||||||
|
WHERE ar2.application_id = ar.application_id
|
||||||
|
AND ar2.deleted = 0) as total_nodes,
|
||||||
|
ar.node_order as current_node,
|
||||||
|
'项目审批' as process_name,
|
||||||
|
(CASE
|
||||||
|
WHEN EXISTS(SELECT 1 FROM cw_approval_record ar3
|
||||||
|
WHERE ar3.application_id = ar.application_id
|
||||||
|
AND ar3.node_status = 'rejected'
|
||||||
|
AND ar3.deleted = 0) THEN 'rejected'
|
||||||
|
WHEN NOT EXISTS(SELECT 1 FROM cw_approval_record ar4
|
||||||
|
WHERE ar4.application_id = ar.application_id
|
||||||
|
AND ar4.node_status IN ('waiting', 'processing')
|
||||||
|
AND ar4.deleted = 0) THEN 'completed'
|
||||||
|
ELSE 'processing'
|
||||||
|
END) as process_status
|
||||||
from cw_approval_record ar
|
from cw_approval_record ar
|
||||||
left join cw_project_application pa on ar.application_id = pa.id
|
left join cw_project_application pa on ar.application_id = pa.id
|
||||||
left join sys_user u on pa.create_by = u.user_id
|
left join sys_user u on pa.create_by = u.user_id
|
||||||
|
|||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="org.dromara.cw.mapper.CwProjectApprovalProcessMapper">
|
||||||
|
|
||||||
|
<resultMap type="org.dromara.cw.domain.vo.CwProjectApprovalProcessVo" id="CwProjectApprovalProcessResult">
|
||||||
|
<result property="id" column="id" />
|
||||||
|
<result property="applicationId" column="application_id" />
|
||||||
|
<result property="applicationNo" column="application_no" />
|
||||||
|
<result property="processName" column="process_name" />
|
||||||
|
<result property="processStatus" column="process_status" />
|
||||||
|
<result property="totalNodes" column="total_nodes" />
|
||||||
|
<result property="currentNode" column="current_node" />
|
||||||
|
<result property="startTime" column="start_time" />
|
||||||
|
<result property="endTime" column="end_time" />
|
||||||
|
<result property="submitterId" column="submitter_id" />
|
||||||
|
<result property="submitterName" column="submitter_name" />
|
||||||
|
<result property="createBy" column="create_by" />
|
||||||
|
<result property="createTime" column="create_time" />
|
||||||
|
<result property="updateBy" column="update_by" />
|
||||||
|
<result property="updateTime" column="update_time" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectCwProjectApprovalProcessVo">
|
||||||
|
select
|
||||||
|
p.id, p.application_id, p.application_no, p.process_name, p.process_status,
|
||||||
|
p.total_nodes, p.current_node, p.start_time, p.end_time,
|
||||||
|
p.submitter_id, p.submitter_name,
|
||||||
|
p.create_by, p.create_time, p.update_by, p.update_time,
|
||||||
|
a.project_name,
|
||||||
|
u.nick_name as submitter_nick_name
|
||||||
|
from cw_project_approval_process p
|
||||||
|
left join cw_project_application a on p.application_id = a.id
|
||||||
|
left join sys_user u on p.submitter_id = u.user_id
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectProcessList" parameterType="org.dromara.cw.domain.CwProjectApprovalProcess" resultMap="CwProjectApprovalProcessResult">
|
||||||
|
<include refid="selectCwProjectApprovalProcessVo"/>
|
||||||
|
<where>
|
||||||
|
p.deleted = 0
|
||||||
|
<if test="applicationId != null"> and p.application_id = #{applicationId}</if>
|
||||||
|
<if test="applicationNo != null and applicationNo != ''"> and p.application_no like concat('%', #{applicationNo}, '%')</if>
|
||||||
|
<if test="processName != null and processName != ''"> and p.process_name like concat('%', #{processName}, '%')</if>
|
||||||
|
<if test="processStatus != null and processStatus != ''"> and p.process_status = #{processStatus}</if>
|
||||||
|
<if test="submitterId != null"> and p.submitter_id = #{submitterId}</if>
|
||||||
|
<if test="submitterName != null and submitterName != ''"> and p.submitter_name like concat('%', #{submitterName}, '%')</if>
|
||||||
|
<if test="params.beginCreateTime != null and params.beginCreateTime != ''"> and p.create_time >= #{params.beginCreateTime}</if>
|
||||||
|
<if test="params.endCreateTime != null and params.endCreateTime != ''"> and p.create_time <= #{params.endCreateTime}</if>
|
||||||
|
<if test="params.beginStartTime != null and params.beginStartTime != ''"> and p.start_time >= #{params.beginStartTime}</if>
|
||||||
|
<if test="params.endStartTime != null and params.endStartTime != ''"> and p.start_time <= #{params.endStartTime}</if>
|
||||||
|
</where>
|
||||||
|
order by p.create_time desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByApplicationId" resultMap="CwProjectApprovalProcessResult">
|
||||||
|
<include refid="selectCwProjectApprovalProcessVo"/>
|
||||||
|
where p.application_id = #{applicationId} and p.deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByApplicationNo" resultMap="CwProjectApprovalProcessResult">
|
||||||
|
<include refid="selectCwProjectApprovalProcessVo"/>
|
||||||
|
where p.application_no = #{applicationNo} and p.deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectMyProcesses" resultMap="CwProjectApprovalProcessResult">
|
||||||
|
<include refid="selectCwProjectApprovalProcessVo"/>
|
||||||
|
where p.submitter_id = #{submitterId} and p.deleted = 0
|
||||||
|
order by p.create_time desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectRunningProcesses" resultMap="CwProjectApprovalProcessResult">
|
||||||
|
<include refid="selectCwProjectApprovalProcessVo"/>
|
||||||
|
where p.process_status = 'running' and p.deleted = 0
|
||||||
|
order by p.start_time
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectOvertimeProcesses" resultMap="CwProjectApprovalProcessResult">
|
||||||
|
<include refid="selectCwProjectApprovalProcessVo"/>
|
||||||
|
where p.process_status = 'running'
|
||||||
|
and p.start_time < date_sub(now(), interval #{hours} hour)
|
||||||
|
and p.deleted = 0
|
||||||
|
order by p.start_time
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectProcessStatistics" resultType="map">
|
||||||
|
select
|
||||||
|
p.process_status as status,
|
||||||
|
count(*) as count
|
||||||
|
from cw_project_approval_process p
|
||||||
|
where p.deleted = 0
|
||||||
|
<if test="submitterId != null"> and p.submitter_id = #{submitterId}</if>
|
||||||
|
group by p.process_status
|
||||||
|
order by count desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="updateCurrentNode">
|
||||||
|
update cw_project_approval_process
|
||||||
|
set current_node = #{currentNode}, update_by = #{updateBy}, update_time = now()
|
||||||
|
where id = #{id} and deleted = 0
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="updateProcessStatus">
|
||||||
|
update cw_project_approval_process
|
||||||
|
set process_status = #{status},
|
||||||
|
end_time = case when #{status} in ('completed', 'terminated') then now() else end_time end,
|
||||||
|
update_by = #{updateBy},
|
||||||
|
update_time = now()
|
||||||
|
where id = #{id} and deleted = 0
|
||||||
|
</update>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="org.dromara.cw.mapper.CwProjectAuditorMapper">
|
||||||
|
|
||||||
|
<resultMap type="org.dromara.cw.domain.vo.CwProjectAuditorVo" id="CwProjectAuditorResult">
|
||||||
|
<result property="id" column="id" />
|
||||||
|
<result property="userId" column="user_id" />
|
||||||
|
<result property="userName" column="user_name" />
|
||||||
|
<result property="nickName" column="nick_name" />
|
||||||
|
<result property="deptId" column="dept_id" />
|
||||||
|
<result property="deptName" column="dept_name" />
|
||||||
|
<result property="phoneNumber" column="phone_number" />
|
||||||
|
<result property="email" column="email" />
|
||||||
|
<result property="status" column="status" />
|
||||||
|
<result property="remark" column="remark" />
|
||||||
|
<result property="configBy" column="config_by" />
|
||||||
|
<result property="configByName" column="config_by_name" />
|
||||||
|
<result property="createBy" column="create_by" />
|
||||||
|
<result property="createTime" column="create_time" />
|
||||||
|
<result property="updateBy" column="update_by" />
|
||||||
|
<result property="updateTime" column="update_time" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectCwProjectAuditorVo">
|
||||||
|
select
|
||||||
|
a.id, a.user_id, a.user_name, a.nick_name, a.dept_id, a.dept_name,
|
||||||
|
a.phone_number, a.email, a.status, a.remark, a.config_by, a.config_by_name,
|
||||||
|
a.create_by, a.create_time, a.update_by, a.update_time,
|
||||||
|
u.user_name as sys_user_name,
|
||||||
|
d.dept_name as sys_dept_name
|
||||||
|
from cw_project_auditor a
|
||||||
|
left join sys_user u on a.user_id = u.user_id
|
||||||
|
left join sys_dept d on a.dept_id = d.dept_id
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectAuditorList" parameterType="org.dromara.cw.domain.CwProjectAuditor" resultMap="CwProjectAuditorResult">
|
||||||
|
<include refid="selectCwProjectAuditorVo"/>
|
||||||
|
<where>
|
||||||
|
a.deleted = 0
|
||||||
|
<if test="userId != null"> and a.user_id = #{userId}</if>
|
||||||
|
<if test="userName != null and userName != ''"> and a.user_name like concat('%', #{userName}, '%')</if>
|
||||||
|
<if test="nickName != null and nickName != ''"> and a.nick_name like concat('%', #{nickName}, '%')</if>
|
||||||
|
<if test="deptId != null"> and a.dept_id = #{deptId}</if>
|
||||||
|
<if test="deptName != null and deptName != ''"> and a.dept_name like concat('%', #{deptName}, '%')</if>
|
||||||
|
<if test="status != null and status != ''"> and a.status = #{status}</if>
|
||||||
|
<if test="configBy != null"> and a.config_by = #{configBy}</if>
|
||||||
|
<if test="configByName != null and configByName != ''"> and a.config_by_name like concat('%', #{configByName}, '%')</if>
|
||||||
|
<if test="params.beginCreateTime != null and params.beginCreateTime != ''"> and a.create_time >= #{params.beginCreateTime}</if>
|
||||||
|
<if test="params.endCreateTime != null and params.endCreateTime != ''"> and a.create_time <= #{params.endCreateTime}</if>
|
||||||
|
</where>
|
||||||
|
order by a.create_time desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectAuditorsByDeptId" resultMap="CwProjectAuditorResult">
|
||||||
|
<include refid="selectCwProjectAuditorVo"/>
|
||||||
|
where a.dept_id = #{deptId} and a.status = '1' and a.deleted = 0
|
||||||
|
order by a.create_time desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectAvailableAuditors" resultMap="CwProjectAuditorResult">
|
||||||
|
<include refid="selectCwProjectAuditorVo"/>
|
||||||
|
where a.status = '1' and a.deleted = 0
|
||||||
|
order by a.dept_id, a.create_time desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectMyConfigAuditors" resultMap="CwProjectAuditorResult">
|
||||||
|
<include refid="selectCwProjectAuditorVo"/>
|
||||||
|
where a.config_by = #{configBy} and a.deleted = 0
|
||||||
|
order by a.create_time desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectAuditorsByUserIds" resultMap="CwProjectAuditorResult">
|
||||||
|
<include refid="selectCwProjectAuditorVo"/>
|
||||||
|
where a.user_id in
|
||||||
|
<foreach item="userId" collection="userIds" open="(" separator="," close=")">
|
||||||
|
#{userId}
|
||||||
|
</foreach>
|
||||||
|
and a.status = '1' and a.deleted = 0
|
||||||
|
order by a.create_time desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectAuditorStatistics" resultType="map">
|
||||||
|
select
|
||||||
|
d.dept_name,
|
||||||
|
count(*) as total_count,
|
||||||
|
count(case when a.status = '1' then 1 end) as active_count,
|
||||||
|
count(case when a.status = '0' then 1 end) as inactive_count
|
||||||
|
from cw_project_auditor a
|
||||||
|
left join sys_dept d on a.dept_id = d.dept_id
|
||||||
|
where a.deleted = 0
|
||||||
|
<if test="deptId != null"> and a.dept_id = #{deptId}</if>
|
||||||
|
<if test="status != null and status != ''"> and a.status = #{status}</if>
|
||||||
|
group by a.dept_id, d.dept_name
|
||||||
|
order by total_count desc
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByUserId" resultMap="CwProjectAuditorResult">
|
||||||
|
<include refid="selectCwProjectAuditorVo"/>
|
||||||
|
where a.user_id = #{userId} and a.deleted = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="batchUpdateStatus">
|
||||||
|
update cw_project_auditor
|
||||||
|
set status = #{status}, update_by = #{updateBy}, update_time = now()
|
||||||
|
where id in
|
||||||
|
<foreach item="id" collection="ids" open="(" separator="," close=")">
|
||||||
|
#{id}
|
||||||
|
</foreach>
|
||||||
|
and deleted = 0
|
||||||
|
</update>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import type { AxiosPromise } from 'axios';
|
||||||
|
import type { PageQuery, TableDataInfo } from '@/api/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程查询参数
|
||||||
|
*/
|
||||||
|
export interface CwProjectApprovalQuery extends PageQuery {
|
||||||
|
applicationNo?: string;
|
||||||
|
projectName?: string;
|
||||||
|
processStatus?: string;
|
||||||
|
submitterName?: string;
|
||||||
|
startTime?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审批流程VO
|
||||||
|
*/
|
||||||
|
export interface CwProjectApprovalProcessVO {
|
||||||
|
id: string | number;
|
||||||
|
applicationId: string | number;
|
||||||
|
applicationNo: string;
|
||||||
|
processName: string;
|
||||||
|
processStatus: string;
|
||||||
|
totalNodes: number;
|
||||||
|
currentNode: number;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
submitterId: string | number;
|
||||||
|
submitterName: string;
|
||||||
|
createTime: string;
|
||||||
|
updateTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批记录VO
|
||||||
|
*/
|
||||||
|
export interface CwApprovalRecordVO {
|
||||||
|
id: string | number;
|
||||||
|
applicationId: string | number;
|
||||||
|
applicationNo: string;
|
||||||
|
processId: string | number;
|
||||||
|
nodeCode: string;
|
||||||
|
nodeName: string;
|
||||||
|
customNodeName: string;
|
||||||
|
nodeOrder: number;
|
||||||
|
approverId: string | number;
|
||||||
|
approverName: string;
|
||||||
|
nodeStatus: string;
|
||||||
|
approveAction: string;
|
||||||
|
approveOpinion: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
isCustomProcess: boolean;
|
||||||
|
createTime: string;
|
||||||
|
updateTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义审批流程启动请求
|
||||||
|
*/
|
||||||
|
export interface CwCustomApprovalRequest {
|
||||||
|
applicationId: string | number;
|
||||||
|
auditorIds: Array<string | number>;
|
||||||
|
processName: string;
|
||||||
|
submitterId: string | number;
|
||||||
|
submitterName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批操作请求
|
||||||
|
*/
|
||||||
|
export interface CwApprovalActionRequest {
|
||||||
|
recordId: string | number;
|
||||||
|
action: string; // approve | reject
|
||||||
|
opinion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审批流程列表
|
||||||
|
*/
|
||||||
|
export function listProjectApproval(query: CwProjectApprovalQuery): AxiosPromise<TableDataInfo<CwProjectApprovalProcessVO>> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取项目审批流程详细信息
|
||||||
|
*/
|
||||||
|
export function getProjectApproval(id: string | number): AxiosPromise<CwProjectApprovalProcessVO> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/' + id,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动自定义审批流程
|
||||||
|
*/
|
||||||
|
export function startCustomApprovalProcess(data: CwCustomApprovalRequest): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/start',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行审批操作(统一接口)
|
||||||
|
*/
|
||||||
|
export function approveProcess(data: CwApprovalActionRequest): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/approve',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打回申请(统一接口)
|
||||||
|
*/
|
||||||
|
export function rejectProcess(data: CwApprovalActionRequest): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/approve',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终止审批流程
|
||||||
|
*/
|
||||||
|
export function terminateProcess(id: string | number): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的待审批列表
|
||||||
|
*/
|
||||||
|
export function getMyPendingApprovals(query?: CwProjectApprovalQuery): AxiosPromise<any> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/pending',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的已审批列表
|
||||||
|
*/
|
||||||
|
export function getMyProcessedApprovals(query?: CwProjectApprovalQuery): AxiosPromise<any> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/processed',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审批记录
|
||||||
|
*/
|
||||||
|
export function getApprovalRecords(applicationId: string | number): AxiosPromise<CwApprovalRecordVO[]> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/records/' + applicationId,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证审批流程
|
||||||
|
*/
|
||||||
|
export function validateApprovalProcess(data: CwCustomApprovalRequest): AxiosPromise<any> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/validate',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审批流程统计数据
|
||||||
|
*/
|
||||||
|
export function getApprovalStatistics(): AxiosPromise<any> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/statistics',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出项目审批流程
|
||||||
|
*/
|
||||||
|
export function exportProjectApproval(query: CwProjectApprovalQuery): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectApproval/export',
|
||||||
|
method: 'post',
|
||||||
|
data: query,
|
||||||
|
responseType: 'blob'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取可用审核人选项
|
||||||
|
*/
|
||||||
|
export function getAuditorOptions(): AxiosPromise<any[]> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/options',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import { AxiosPromise } from 'axios';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审核人配置VO对象
|
||||||
|
*/
|
||||||
|
export interface CwProjectAuditorVO {
|
||||||
|
id: string | number;
|
||||||
|
userId: string | number;
|
||||||
|
userName: string;
|
||||||
|
nickName: string;
|
||||||
|
deptId: string | number;
|
||||||
|
deptName: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
email: string;
|
||||||
|
status: string;
|
||||||
|
remark: string;
|
||||||
|
configBy: string | number;
|
||||||
|
configByName: string;
|
||||||
|
createTime: string;
|
||||||
|
updateTime: string;
|
||||||
|
createBy: string | number;
|
||||||
|
updateBy: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审核人配置表单对象
|
||||||
|
*/
|
||||||
|
export interface CwProjectAuditorForm {
|
||||||
|
id?: string | number;
|
||||||
|
userId: string | number;
|
||||||
|
userName: string;
|
||||||
|
nickName: string;
|
||||||
|
deptId: string | number;
|
||||||
|
deptName: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
email?: string;
|
||||||
|
status: string;
|
||||||
|
remark?: string;
|
||||||
|
configBy?: string | number;
|
||||||
|
configByName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目审核人配置查询对象
|
||||||
|
*/
|
||||||
|
export interface CwProjectAuditorQuery {
|
||||||
|
pageNum: number;
|
||||||
|
pageSize: number;
|
||||||
|
userName?: string;
|
||||||
|
nickName?: string;
|
||||||
|
deptId?: string | number;
|
||||||
|
status?: string;
|
||||||
|
configBy?: string | number;
|
||||||
|
createTime?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加请求对象
|
||||||
|
*/
|
||||||
|
export interface BatchAddRequest {
|
||||||
|
userIds: Array<string | number>;
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态更新请求对象
|
||||||
|
*/
|
||||||
|
export interface StatusUpdateRequest {
|
||||||
|
ids: Array<string | number>;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门用户对象
|
||||||
|
*/
|
||||||
|
export interface DeptUserVO {
|
||||||
|
userId: string | number;
|
||||||
|
userName: string;
|
||||||
|
nickName: string;
|
||||||
|
deptId: string | number;
|
||||||
|
deptName: string;
|
||||||
|
phonenumber: string;
|
||||||
|
email: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审核人配置列表
|
||||||
|
*/
|
||||||
|
export function listProjectAuditor(query: CwProjectAuditorQuery): AxiosPromise<{ rows: CwProjectAuditorVO[]; total: number }> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询项目审核人配置详细
|
||||||
|
*/
|
||||||
|
export function getProjectAuditor(id: string | number): AxiosPromise<CwProjectAuditorVO> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/' + id,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增项目审核人配置
|
||||||
|
*/
|
||||||
|
export function addProjectAuditor(data: CwProjectAuditorForm): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改项目审核人配置
|
||||||
|
*/
|
||||||
|
export function updateProjectAuditor(data: CwProjectAuditorForm): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除项目审核人配置
|
||||||
|
*/
|
||||||
|
export function delProjectAuditor(ids: Array<string | number>): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/' + ids.join(','),
|
||||||
|
method: 'delete'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加审核人
|
||||||
|
*/
|
||||||
|
export function batchAddProjectAuditor(data: BatchAddRequest): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/batchAdd',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取可用审核人列表
|
||||||
|
*/
|
||||||
|
export function listAvailableAuditors(): AxiosPromise<CwProjectAuditorVO[]> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/available',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据部门ID获取用户列表
|
||||||
|
*/
|
||||||
|
export function listUserByDeptId(deptId: string | number): AxiosPromise<DeptUserVO[]> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/deptUsers/' + deptId,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新状态
|
||||||
|
*/
|
||||||
|
export function updateAuditorStatus(data: StatusUpdateRequest): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/status',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 我配置的审核人
|
||||||
|
*/
|
||||||
|
export function listMyConfigAuditors(query: CwProjectAuditorQuery): AxiosPromise<{ rows: CwProjectAuditorVO[]; total: number }> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/myConfig',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审核人选项(用于下拉选择)
|
||||||
|
*/
|
||||||
|
export function listAuditorOptions(): AxiosPromise<CwProjectAuditorVO[]> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/options',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量启用
|
||||||
|
*/
|
||||||
|
export function enableAuditors(ids: Array<string | number>): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/enable/' + ids.join(','),
|
||||||
|
method: 'put'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量停用
|
||||||
|
*/
|
||||||
|
export function disableAuditors(ids: Array<string | number>): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/disable/' + ids.join(','),
|
||||||
|
method: 'put'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出项目审核人配置
|
||||||
|
*/
|
||||||
|
export function exportProjectAuditor(query: CwProjectAuditorQuery): AxiosPromise<void> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/export',
|
||||||
|
method: 'post',
|
||||||
|
data: query,
|
||||||
|
responseType: 'blob'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审核人统计数据
|
||||||
|
*/
|
||||||
|
export function getAuditorStatistics(): AxiosPromise<any> {
|
||||||
|
return request({
|
||||||
|
url: '/cw/projectAuditor/statistics',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -203,3 +203,183 @@ export interface StatusDistribution {
|
|||||||
count: number;
|
count: number;
|
||||||
percentage: number;
|
percentage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 项目审批人配置相关类型 ====================
|
||||||
|
|
||||||
|
// 项目审批人配置查询对象
|
||||||
|
export interface CwProjectAuditorQuery {
|
||||||
|
pageNum?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
userName?: string;
|
||||||
|
nickName?: string;
|
||||||
|
deptId?: string | number;
|
||||||
|
status?: string;
|
||||||
|
configTime?: [string, string];
|
||||||
|
configBy?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 项目审批人配置视图对象
|
||||||
|
export interface CwProjectAuditorVO {
|
||||||
|
id?: string | number;
|
||||||
|
userId?: string | number;
|
||||||
|
userName?: string;
|
||||||
|
nickName?: string;
|
||||||
|
deptId?: string | number;
|
||||||
|
deptName?: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
email?: string;
|
||||||
|
status?: string;
|
||||||
|
remark?: string;
|
||||||
|
configBy?: string | number;
|
||||||
|
configByName?: string;
|
||||||
|
createBy?: string | number;
|
||||||
|
createTime?: string;
|
||||||
|
updateBy?: string | number;
|
||||||
|
updateTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 项目审批人配置表单对象
|
||||||
|
export interface CwProjectAuditorForm {
|
||||||
|
id?: string | number;
|
||||||
|
userId?: string | number;
|
||||||
|
userName?: string;
|
||||||
|
nickName?: string;
|
||||||
|
deptId?: string | number;
|
||||||
|
deptName?: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
email?: string;
|
||||||
|
status?: string;
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量添加审批人业务对象
|
||||||
|
export interface CwProjectAuditorBatchBo {
|
||||||
|
userIds: Array<string | number>;
|
||||||
|
configBy: string | number;
|
||||||
|
configByName: string;
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 项目审批流程相关类型 ====================
|
||||||
|
|
||||||
|
// 审批流程查询对象
|
||||||
|
export interface CwProjectApprovalProcessQuery {
|
||||||
|
pageNum?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
applicationNo?: string;
|
||||||
|
processName?: string;
|
||||||
|
processStatus?: string;
|
||||||
|
submitterId?: string | number;
|
||||||
|
submitterName?: string;
|
||||||
|
startTime?: [string, string];
|
||||||
|
endTime?: [string, string];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批流程视图对象
|
||||||
|
export interface CwProjectApprovalProcessVO {
|
||||||
|
id?: string | number;
|
||||||
|
applicationId?: string | number;
|
||||||
|
applicationNo?: string;
|
||||||
|
processName?: string;
|
||||||
|
processStatus?: string;
|
||||||
|
totalNodes?: number;
|
||||||
|
currentNode?: number;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
submitterId?: string | number;
|
||||||
|
submitterName?: string;
|
||||||
|
createBy?: string | number;
|
||||||
|
createTime?: string;
|
||||||
|
updateBy?: string | number;
|
||||||
|
updateTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批流程表单对象
|
||||||
|
export interface CwProjectApprovalProcessForm {
|
||||||
|
id?: string | number;
|
||||||
|
applicationId?: string | number;
|
||||||
|
applicationNo?: string;
|
||||||
|
processName?: string;
|
||||||
|
processStatus?: string;
|
||||||
|
totalNodes?: number;
|
||||||
|
currentNode?: number;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
submitterId?: string | number;
|
||||||
|
submitterName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批记录查询对象
|
||||||
|
export interface CwApprovalRecordQuery {
|
||||||
|
pageNum?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
applicationId?: string | number;
|
||||||
|
applicationNo?: string;
|
||||||
|
processId?: string | number;
|
||||||
|
approverId?: string | number;
|
||||||
|
nodeStatus?: string;
|
||||||
|
approveAction?: string;
|
||||||
|
isCustomProcess?: boolean;
|
||||||
|
startTime?: [string, string];
|
||||||
|
endTime?: [string, string];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批记录视图对象
|
||||||
|
export interface CwApprovalRecordVO {
|
||||||
|
id?: string | number;
|
||||||
|
applicationId?: string | number;
|
||||||
|
applicationNo?: string;
|
||||||
|
processId?: string | number;
|
||||||
|
nodeCode?: string;
|
||||||
|
nodeName?: string;
|
||||||
|
customNodeName?: string;
|
||||||
|
nodeOrder?: number;
|
||||||
|
approverId?: string | number;
|
||||||
|
approverName?: string;
|
||||||
|
nodeStatus?: string;
|
||||||
|
approveAction?: string;
|
||||||
|
approveOpinion?: string;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
isCustomProcess?: boolean;
|
||||||
|
createBy?: string | number;
|
||||||
|
createTime?: string;
|
||||||
|
updateBy?: string | number;
|
||||||
|
updateTime?: string;
|
||||||
|
// 关联的申请信息
|
||||||
|
projectName?: string;
|
||||||
|
submitterName?: string;
|
||||||
|
totalInvestment?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义审批业务对象
|
||||||
|
export interface CwCustomApprovalBo {
|
||||||
|
applicationId: string | number;
|
||||||
|
auditorIds: Array<string | number>;
|
||||||
|
processName?: string;
|
||||||
|
submitterId?: string | number;
|
||||||
|
submitterName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批操作业务对象
|
||||||
|
export interface CwApprovalActionBo {
|
||||||
|
recordId: string | number;
|
||||||
|
action: string; // 'approve' | 'reject'
|
||||||
|
opinion: string;
|
||||||
|
approverId?: string | number;
|
||||||
|
approverName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// 扩展审批记录视图对象
|
||||||
|
// =====================================================
|
||||||
|
export interface CwApprovalRecordExtendedVO extends CwApprovalRecordVO {
|
||||||
|
processId?: string | number;
|
||||||
|
customNodeName?: string;
|
||||||
|
isCustomProcess?: boolean;
|
||||||
|
|
||||||
|
// 关联项目信息(用于显示)
|
||||||
|
projectName?: string;
|
||||||
|
totalInvestment?: number;
|
||||||
|
applicantName?: string;
|
||||||
|
}
|
||||||
@@ -71,3 +71,11 @@ export const delDept = (deptId: number | string) => {
|
|||||||
method: 'delete'
|
method: 'delete'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 查询部门选项列表 (实际调用 list 接口)
|
||||||
|
export const listDeptOptions = (): AxiosPromise<DeptVO[]> => {
|
||||||
|
return request({
|
||||||
|
url: '/system/dept/list',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -67,6 +67,14 @@ export interface R<T = any> {
|
|||||||
data: T;
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询基础接口
|
||||||
|
*/
|
||||||
|
export interface PageQuery {
|
||||||
|
pageNum?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页数据信息
|
* 分页数据信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
+171
-36
@@ -1,55 +1,190 @@
|
|||||||
# 财务管理模块 - 前端界面
|
# 财务管理模块 (CW) - 项目申报审批功能
|
||||||
|
|
||||||
## 📋 模块说明
|
## 🎯 功能概述
|
||||||
|
|
||||||
- **模块名称**: 财务管理 (Financial Management)
|
本次开发实现了项目申报管理模块的审批流程变更,从固定的三级审批改为支持用户自定义的灵活审批流程。
|
||||||
- **模块简称**: cw
|
|
||||||
- **对应后端**: backend/ruoyi-modules/ruoyi-cw/
|
|
||||||
|
|
||||||
## 📁 目录结构
|
## 📁 目录结构
|
||||||
|
|
||||||
此目录用于存放财务管理模块的所有前端界面组件,包括:
|
```
|
||||||
|
web/src/views/cw/
|
||||||
|
├── projectAuditor/ # 项目审批人配置模块
|
||||||
|
│ ├── index.vue # 审批人配置主页面
|
||||||
|
│ └── README.md # 功能说明文档
|
||||||
|
├── projectApproval/ # 项目审批模块
|
||||||
|
│ ├── index.vue # 审批流程管理页面(待实现)
|
||||||
|
│ ├── pending.vue # 我的待审批页面
|
||||||
|
│ ├── components/
|
||||||
|
│ │ └── ApprovalProcessSelector.vue # 审批流程选择组件
|
||||||
|
│ └── README.md # 功能说明文档(待创建)
|
||||||
|
├── application/ # 项目申报模块(已修改)
|
||||||
|
│ ├── form.vue # 申报表单(已集成审批流程选择)
|
||||||
|
│ └── ... # 其他文件保持不变
|
||||||
|
└── README.md # 总体说明文档(本文件)
|
||||||
|
```
|
||||||
|
|
||||||
### 💰 会计核算
|
```
|
||||||
|
web/src/api/cw/
|
||||||
|
├── projectAuditor.ts # 审批人配置API
|
||||||
|
├── projectApproval.ts # 项目审批API
|
||||||
|
└── types.ts # TypeScript类型定义(已扩展)
|
||||||
|
```
|
||||||
|
|
||||||
- 科目管理
|
## 🚀 核心功能
|
||||||
- 凭证管理
|
|
||||||
- 账簿管理
|
|
||||||
|
|
||||||
### 📊 预算管理
|
### 1. 项目审批人配置管理 (`projectAuditor/`)
|
||||||
|
- **审批人池管理**: 从系统用户中选择并配置审批人员
|
||||||
|
- **批量添加**: 通过部门树选择用户,支持批量添加审批人
|
||||||
|
- **跨部门选择**: 支持在不同部门间选择用户,选择状态会自动保持
|
||||||
|
- **信息维护**: 修改审批人联系信息、状态管理
|
||||||
|
- **权限控制**: 基于RBAC的细粒度权限控制
|
||||||
|
|
||||||
- 预算编制
|
### 2. 项目审批流程管理 (`projectApproval/`)
|
||||||
- 预算执行
|
- **流程监控**: 查看所有审批流程状态和进度(待实现)
|
||||||
- 预算分析
|
- **流程详情**: 查看审批记录和时间轴展示(待实现)
|
||||||
|
- **流程终止**: 管理员可终止进行中的审批流程(待实现)
|
||||||
|
|
||||||
### 💳 资产管理
|
### 3. 我的待审批 (`projectApproval/pending.vue`)
|
||||||
|
- **待审批列表**: 显示当前用户需要审批的项目
|
||||||
|
- **项目详情**: 查看项目完整信息和审批历史
|
||||||
|
- **审批操作**: 支持通过/拒绝,必填审批意见
|
||||||
|
- **批量审批**: 支持批量通过或拒绝多个项目(待实现)
|
||||||
|
|
||||||
- 固定资产
|
### 4. 审批流程选择组件 (`components/ApprovalProcessSelector.vue`)
|
||||||
- 流动资产
|
- **自定义流程配置**: 支持用户从审批人池中选择审核人
|
||||||
- 资产盘点
|
- **可视化流程预览**: 直观展示审批顺序和流程走向
|
||||||
|
- **审核人管理**: 支持添加、删除、排序审核人
|
||||||
|
- **流程验证**: 验证审批流程的合理性和完整性
|
||||||
|
|
||||||
### 📈 财务报表
|
### 5. 项目申报集成 (`application/form.vue` - 已修改)
|
||||||
|
- **流程选择对话框**: 在提交时弹出审批流程选择界面
|
||||||
|
- **数据保存**: 先保存申报数据,再配置审批流程
|
||||||
|
- **流程启动**: 自动启动配置的审批流程
|
||||||
|
- **用户体验**: 平滑的提交流程,清晰的操作提示
|
||||||
|
|
||||||
- 资产负债表
|
## 📋 实现特色
|
||||||
- 损益表
|
|
||||||
- 现金流量表
|
|
||||||
|
|
||||||
### 💸 费用管理
|
### 🔧 技术特色
|
||||||
|
1. **Vue 3.5.13 Composition API**: 使用最新的组合式API,代码结构清晰
|
||||||
|
2. **TypeScript支持**: 完整的类型定义,提供良好的开发体验
|
||||||
|
3. **Element Plus UI**: 统一的UI组件库,符合后勤管理模块设计规范
|
||||||
|
4. **响应式设计**: 支持移动端和桌面端,适配不同屏幕尺寸
|
||||||
|
5. **组件化开发**: 高度复用的组件设计,易于维护和扩展
|
||||||
|
|
||||||
- 费用申请
|
### 🎨 用户体验特色
|
||||||
- 费用审批
|
1. **跨部门选择**: 审批人选择支持在多个部门间切换,保持选择状态
|
||||||
- 费用报销
|
2. **可视化流程**: 审批流程以图形化方式展示,直观易懂
|
||||||
|
3. **实时验证**: 表单验证和流程验证,及时提供反馈
|
||||||
|
4. **操作引导**: 清晰的操作提示和帮助信息
|
||||||
|
5. **状态恢复**: 切换部门后自动恢复之前的选择状态
|
||||||
|
|
||||||
## 🎯 开发规范
|
### 🚀 性能特色
|
||||||
|
1. **按需加载**: 组件和API按需加载,优化性能
|
||||||
|
2. **智能缓存**: 合理的数据缓存策略,减少重复请求
|
||||||
|
3. **批量操作**: 支持批量添加、删除等操作,提升效率
|
||||||
|
4. **异步处理**: 异步API调用,不阻塞用户界面
|
||||||
|
|
||||||
- 遵循Vue 3 + TypeScript + Element Plus开发规范
|
## 🔄 核心流程
|
||||||
- 组件命名采用PascalCase
|
|
||||||
- 文件命名采用kebab-case
|
|
||||||
- 统一使用Composition API
|
|
||||||
|
|
||||||
## 📝 注意事项
|
### 审批人配置流程
|
||||||
|
```
|
||||||
|
管理员登录 → 进入审批人配置 → 选择部门 → 批量选择用户 → 确认添加 → 配置完成
|
||||||
|
```
|
||||||
|
|
||||||
- 新增页面需要配置对应的路由
|
### 项目申报审批流程
|
||||||
- API接口统一调用@/api/cw/目录下的接口
|
```
|
||||||
- 权限控制使用v-hasPermi指令,权限编码格式:cw:功能:操作
|
填写申报信息 → 点击提交 → 选择审批流程 → 配置审核人 → 确认提交 → 流程启动
|
||||||
- 财务数据需要特别注意精度和安全性
|
```
|
||||||
|
|
||||||
|
### 审批执行流程
|
||||||
|
```
|
||||||
|
审核人收到通知 → 查看待审批列表 → 查看项目详情 → 执行审批操作 → 填写意见 → 提交审批
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ 后端支持
|
||||||
|
|
||||||
|
### 数据库设计
|
||||||
|
1. **cw_project_auditor**: 项目审核人配置表
|
||||||
|
2. **cw_project_approval_process**: 项目审批流程表
|
||||||
|
3. **cw_approval_record**: 审批记录表(已扩展支持自定义流程)
|
||||||
|
|
||||||
|
### API接口
|
||||||
|
1. **审核人配置**: 16个标准API接口(CRUD + 业务功能 + 辅助功能)
|
||||||
|
2. **审批流程**: 10个核心API接口(流程管理 + 审批执行)
|
||||||
|
3. **权限控制**: 基于Sa-Token的权限管理
|
||||||
|
|
||||||
|
### 数据安全
|
||||||
|
1. **多租户隔离**: 严格的数据隔离机制
|
||||||
|
2. **权限控制**: 细粒度的功能权限控制
|
||||||
|
3. **操作日志**: 完整的操作记录和审计
|
||||||
|
|
||||||
|
## 📊 技术规范
|
||||||
|
|
||||||
|
### 前端规范
|
||||||
|
- **组件命名**: PascalCase,清晰的语义化命名
|
||||||
|
- **文件组织**: 按功能模块组织,层次分明
|
||||||
|
- **样式管理**: Scoped CSS,避免样式冲突
|
||||||
|
- **状态管理**: Reactive响应式状态,简洁高效
|
||||||
|
|
||||||
|
### 后端规范
|
||||||
|
- **接口设计**: RESTful风格,统一响应格式
|
||||||
|
- **数据验证**: Bean Validation参数校验
|
||||||
|
- **异常处理**: 统一的异常处理机制
|
||||||
|
- **事务管理**: 合理的事务边界控制
|
||||||
|
|
||||||
|
### 代码质量
|
||||||
|
- **TypeScript**: 完整的类型检查
|
||||||
|
- **ESLint**: 代码风格检查
|
||||||
|
- **Prettier**: 代码格式化
|
||||||
|
- **单元测试**: 核心逻辑单元测试(待实现)
|
||||||
|
|
||||||
|
## 🎯 后续优化
|
||||||
|
|
||||||
|
### 功能扩展
|
||||||
|
1. **审批流程管理页面**: 完整的流程监控和管理界面
|
||||||
|
2. **批量审批功能**: 支持批量处理多个项目
|
||||||
|
3. **审批历史查询**: 完整的审批记录查询功能
|
||||||
|
4. **移动端支持**: 基于uni-app的移动端审批应用
|
||||||
|
|
||||||
|
### 性能优化
|
||||||
|
1. **虚拟滚动**: 大数据量列表的性能优化
|
||||||
|
2. **缓存策略**: 更智能的数据缓存机制
|
||||||
|
3. **懒加载**: 按需加载组件和数据
|
||||||
|
4. **CDN集成**: 静态资源CDN加速
|
||||||
|
|
||||||
|
### 用户体验
|
||||||
|
1. **快捷操作**: 键盘快捷键支持
|
||||||
|
2. **拖拽排序**: 审核人拖拽排序功能
|
||||||
|
3. **消息通知**: 实时消息推送
|
||||||
|
4. **离线支持**: PWA离线功能支持
|
||||||
|
|
||||||
|
## 🚀 部署说明
|
||||||
|
|
||||||
|
### 开发环境
|
||||||
|
1. 确保Node.js 20.15.x+,npm 10.7.x+
|
||||||
|
2. 安装依赖:`npm install`
|
||||||
|
3. 启动开发服务器:`npm run dev`
|
||||||
|
4. 访问:http://localhost
|
||||||
|
|
||||||
|
### 生产环境
|
||||||
|
1. 构建项目:`npm run build`
|
||||||
|
2. 部署到Nginx或其他Web服务器
|
||||||
|
3. 配置反向代理到后端API
|
||||||
|
4. 确保数据库脚本已执行
|
||||||
|
|
||||||
|
### 数据库部署
|
||||||
|
1. 执行数据库脚本:`docs/数据库修复脚本.sql`
|
||||||
|
2. 配置权限菜单和角色权限
|
||||||
|
3. 创建测试用户和审核人数据
|
||||||
|
|
||||||
|
## 📞 技术支持
|
||||||
|
|
||||||
|
如有任何技术问题或建议,请联系开发团队。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**🎯 目标达成**: 成功实现了从固定三级审批到灵活自定义审批流程的完整改造,提供了良好的用户体验和技术架构!
|
||||||
|
|
||||||
|
**📝 维护日期**: 2025-01-03
|
||||||
|
**📁 文件**: 完整的前后端实现 + 数据库脚本 + API文档
|
||||||
|
**🔄 状态**: 核心功能完成,待进一步测试和优化
|
||||||
@@ -480,17 +480,59 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 审批流程选择对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
title="选择审批流程"
|
||||||
|
v-model="approvalDialog.visible"
|
||||||
|
width="70%"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<div class="approval-dialog-content">
|
||||||
|
<el-alert
|
||||||
|
title="请选择审批流程"
|
||||||
|
description="项目申报提交后将按照您选择的审批流程进行审批,请仔细选择审核人员。"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
style="margin-bottom: 20px"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ApprovalProcessSelector ref="approvalProcessRef" v-model="approvalDialog.selectedAuditors" @change="handleApprovalProcessChange" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="cancelApprovalSubmit">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="confirmApprovalSubmit"
|
||||||
|
:loading="approvalDialog.submitting"
|
||||||
|
:disabled="approvalDialog.selectedAuditors.length === 0"
|
||||||
|
>
|
||||||
|
<el-icon><Check /></el-icon>
|
||||||
|
确认提交 ({{ approvalDialog.selectedAuditors.length }} 个审核人)
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue';
|
import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
import { useUserStore } from '@/store/modules/user';
|
||||||
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
|
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
|
||||||
import { Document, User, Phone, Money, TrendCharts, Check, Calendar, ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
|
import { Document, User, Phone, Money, TrendCharts, Check, Calendar, ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import { getProjectApplication, addProjectApplication, updateProjectApplication, submitApplication, saveDraft } from '@/api/cw/projectApplication';
|
import { getProjectApplication, addProjectApplication, updateProjectApplication, submitApplication, saveDraft } from '@/api/cw/projectApplication';
|
||||||
|
import { startCustomApprovalProcess } from '@/api/cw/projectApproval';
|
||||||
import PerformanceIndicators from './components/PerformanceIndicators.vue';
|
import PerformanceIndicators from './components/PerformanceIndicators.vue';
|
||||||
|
import ApprovalProcessSelector from '../projectApproval/components/ApprovalProcessSelector.vue';
|
||||||
|
|
||||||
// 接收父组件传递的数据
|
// 接收父组件传递的数据
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -499,6 +541,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// 表单引用
|
// 表单引用
|
||||||
const basicFormRef = ref();
|
const basicFormRef = ref();
|
||||||
@@ -506,6 +549,7 @@ const contentFormRef = ref();
|
|||||||
const budgetFormRef = ref();
|
const budgetFormRef = ref();
|
||||||
const performanceFormRef = ref();
|
const performanceFormRef = ref();
|
||||||
const performanceIndicatorsRef = ref();
|
const performanceIndicatorsRef = ref();
|
||||||
|
const approvalProcessRef = ref();
|
||||||
|
|
||||||
// 当前步骤
|
// 当前步骤
|
||||||
const currentStep = ref(0);
|
const currentStep = ref(0);
|
||||||
@@ -515,6 +559,13 @@ const activeTab = ref('quantity');
|
|||||||
const savingDraft = ref(false);
|
const savingDraft = ref(false);
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
|
|
||||||
|
// 审批流程对话框
|
||||||
|
const approvalDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
submitting: false,
|
||||||
|
selectedAuditors: [] as any[]
|
||||||
|
});
|
||||||
|
|
||||||
// 图表引用
|
// 图表引用
|
||||||
const budgetChart = ref();
|
const budgetChart = ref();
|
||||||
let budgetChartInstance: echarts.ECharts | null = null;
|
let budgetChartInstance: echarts.ECharts | null = null;
|
||||||
@@ -1024,17 +1075,9 @@ const handleSubmit = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('开始提交审批,ID:', form.id);
|
// 显示审批流程选择对话框
|
||||||
const submitResult = await submitApplication(form.id);
|
console.log('准备显示审批流程选择对话框,ID:', form.id);
|
||||||
console.log('提交审批结果:', submitResult);
|
showApprovalProcessDialog();
|
||||||
|
|
||||||
if (submitResult.code === 200) {
|
|
||||||
ElMessage.success('申报提交成功');
|
|
||||||
router.push('/cw/application');
|
|
||||||
} else {
|
|
||||||
console.error('提交审批失败:', submitResult);
|
|
||||||
ElMessage.error('提交申报失败: ' + (submitResult.msg || '未知错误'));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('提交申报失败详细错误:', error);
|
console.error('提交申报失败详细错误:', error);
|
||||||
|
|
||||||
@@ -1061,6 +1104,105 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 显示审批流程选择对话框
|
||||||
|
const showApprovalProcessDialog = () => {
|
||||||
|
approvalDialog.visible = true;
|
||||||
|
approvalDialog.selectedAuditors = [];
|
||||||
|
approvalDialog.submitting = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 审批流程变化处理
|
||||||
|
const handleApprovalProcessChange = (auditors: any[]) => {
|
||||||
|
approvalDialog.selectedAuditors = auditors;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消审批提交
|
||||||
|
const cancelApprovalSubmit = () => {
|
||||||
|
approvalDialog.visible = false;
|
||||||
|
submitting.value = false;
|
||||||
|
ElMessage.info('已取消提交');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认审批提交
|
||||||
|
const confirmApprovalSubmit = async () => {
|
||||||
|
try {
|
||||||
|
// 验证审批流程
|
||||||
|
if (!approvalProcessRef.value) {
|
||||||
|
ElMessage.error('审批流程组件初始化失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validation = approvalProcessRef.value.validateProcess();
|
||||||
|
if (!validation.valid) {
|
||||||
|
ElMessage.warning(validation.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
approvalDialog.submitting = true;
|
||||||
|
|
||||||
|
// 确保用户信息已加载
|
||||||
|
if (!userStore.userId || !userStore.name) {
|
||||||
|
ElMessage.error('用户信息未加载,请刷新页面重试');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保申报编号存在
|
||||||
|
if (!form.applicationNo) {
|
||||||
|
ElMessage.error('申报编号未生成,请先保存申报信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取审批流程配置
|
||||||
|
const processConfig = approvalProcessRef.value.getProcessConfig();
|
||||||
|
|
||||||
|
// 构建审批流程数据
|
||||||
|
const approvalData = {
|
||||||
|
applicationId: form.id,
|
||||||
|
applicationNo: form.applicationNo, // 新增:申报编号
|
||||||
|
auditorIds: processConfig.auditorIds,
|
||||||
|
processName: processConfig.processName || `${form.projectName}审批流程`,
|
||||||
|
submitterId: userStore.userId,
|
||||||
|
submitterName: userStore.name
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('启动审批流程,数据:', approvalData);
|
||||||
|
|
||||||
|
// 启动审批流程
|
||||||
|
const result = await startCustomApprovalProcess(approvalData);
|
||||||
|
console.log('启动审批流程结果:', result);
|
||||||
|
|
||||||
|
if (result.code === 200) {
|
||||||
|
ElMessage.success('项目申报提交成功,审批流程已启动');
|
||||||
|
approvalDialog.visible = false;
|
||||||
|
router.push('/cw/application');
|
||||||
|
} else {
|
||||||
|
console.error('启动审批流程失败:', result);
|
||||||
|
ElMessage.error('启动审批流程失败: ' + (result.msg || '未知错误'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('启动审批流程失败详细错误:', error);
|
||||||
|
|
||||||
|
let errorMessage = '启动审批流程失败';
|
||||||
|
if (error && typeof error === 'object' && 'response' in error) {
|
||||||
|
const axiosError = error as any;
|
||||||
|
if (axiosError.response?.data?.msg) {
|
||||||
|
errorMessage += ': ' + axiosError.response.data.msg;
|
||||||
|
}
|
||||||
|
} else if (error && typeof error === 'object' && 'message' in error) {
|
||||||
|
const generalError = error as any;
|
||||||
|
if (generalError.message) {
|
||||||
|
errorMessage += ': ' + generalError.message;
|
||||||
|
}
|
||||||
|
} else if (typeof error === 'string') {
|
||||||
|
errorMessage += ': ' + error;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.error(errorMessage);
|
||||||
|
} finally {
|
||||||
|
approvalDialog.submitting = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 取消操作
|
// 取消操作
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
ElMessageBox.confirm('确认取消吗?未保存的数据将丢失。', '提示', {
|
ElMessageBox.confirm('确认取消吗?未保存的数据将丢失。', '提示', {
|
||||||
|
|||||||
@@ -245,13 +245,13 @@
|
|||||||
步骤 {{ index + 1 }}: {{ step.stepName || '未命名步骤' }}
|
步骤 {{ index + 1 }}: {{ step.stepName || '未命名步骤' }}
|
||||||
</span>
|
</span>
|
||||||
<div class="step-actions">
|
<div class="step-actions">
|
||||||
<el-button type="text" size="small" @click="moveStepUp(index)" :disabled="index === 0">
|
<el-button link size="small" @click="moveStepUp(index)" :disabled="index === 0">
|
||||||
<el-icon><ArrowUp /></el-icon>
|
<el-icon><ArrowUp /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="text" size="small" @click="moveStepDown(index)" :disabled="index === form.steps.length - 1">
|
<el-button link size="small" @click="moveStepDown(index)" :disabled="index === form.steps.length - 1">
|
||||||
<el-icon><ArrowDown /></el-icon>
|
<el-icon><ArrowDown /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="text" size="small" @click="removeStep(index)">
|
<el-button link size="small" @click="removeStep(index)">
|
||||||
<el-icon><Delete /></el-icon>
|
<el-icon><Delete /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
# 项目审批管理模块
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
项目审批管理模块用于管理项目申报的审批流程,支持自定义审批流程配置、审批执行、流程跟踪等功能。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
### 1. 审批流程管理
|
||||||
|
- 查询审批流程列表(支持分页)
|
||||||
|
- 按申报编号、流程状态、提交人等条件筛选
|
||||||
|
- 显示审批进度和流程状态
|
||||||
|
- 支持终止正在进行的审批流程
|
||||||
|
|
||||||
|
### 2. 待我审批
|
||||||
|
- 查看分配给当前用户的待审批任务
|
||||||
|
- 执行审批操作(通过/打回)
|
||||||
|
- 填写审批意见
|
||||||
|
- 查看申报详情和审批记录
|
||||||
|
|
||||||
|
### 3. 我的已审批
|
||||||
|
- 查看当前用户已完成的审批记录
|
||||||
|
- 显示审批动作和意见
|
||||||
|
- 统计审批效率和处理时长
|
||||||
|
|
||||||
|
### 4. 审批记录跟踪
|
||||||
|
- 完整的审批时间线展示
|
||||||
|
- 每个审批节点的详细信息
|
||||||
|
- 审批意见和处理结果记录
|
||||||
|
|
||||||
|
## 页面结构
|
||||||
|
|
||||||
|
```
|
||||||
|
web/src/views/cw/projectApproval/
|
||||||
|
├── index.vue # 主页面组件
|
||||||
|
└── README.md # 功能说明文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
### 1. 多标签页设计
|
||||||
|
- **审批流程管理**: 查看所有审批流程的整体情况
|
||||||
|
- **待我审批**: 专注于当前用户需要处理的审批任务
|
||||||
|
- **我的已审批**: 查看历史审批记录和统计
|
||||||
|
|
||||||
|
### 2. 统计卡片
|
||||||
|
- 总流程数、等待开始、进行中、已完成等状态统计
|
||||||
|
- 平均处理时长、待我审批数量等关键指标
|
||||||
|
- 本月完成数量、已终止流程等趋势数据
|
||||||
|
|
||||||
|
### 3. 审批操作
|
||||||
|
- 支持批量审批和单个审批
|
||||||
|
- 必填审批意见,确保审批质量
|
||||||
|
- 实时更新审批状态和流程进度
|
||||||
|
|
||||||
|
### 4. 流程可视化
|
||||||
|
- 进度条显示审批进度
|
||||||
|
- 时间线展示审批历史
|
||||||
|
- 状态标签清晰标识各阶段
|
||||||
|
|
||||||
|
## API接口
|
||||||
|
|
||||||
|
使用的主要API接口:
|
||||||
|
- `listProjectApproval` - 查询审批流程列表
|
||||||
|
- `listPendingApprovals` - 查询待我审批列表
|
||||||
|
- `listProcessedApprovals` - 查询我的已审批列表
|
||||||
|
- `processApproval` - 执行审批操作
|
||||||
|
- `rejectApproval` - 打回申请
|
||||||
|
- `terminateApproval` - 终止审批流程
|
||||||
|
- `listApprovalRecordsByApplicationId` - 查询审批记录
|
||||||
|
|
||||||
|
## 权限控制
|
||||||
|
|
||||||
|
页面功能对应的权限标识:
|
||||||
|
- `cw:projectApproval:list` - 查询权限
|
||||||
|
- `cw:projectApproval:add` - 启动流程权限
|
||||||
|
- `cw:projectApproval:approve` - 审批权限
|
||||||
|
- `cw:projectApproval:remove` - 终止流程权限
|
||||||
|
- `cw:projectApproval:export` - 导出权限
|
||||||
|
|
||||||
|
## 状态说明
|
||||||
|
|
||||||
|
### 流程状态
|
||||||
|
- `waiting` - 等待开始
|
||||||
|
- `running` - 进行中
|
||||||
|
- `completed` - 已完成
|
||||||
|
- `terminated` - 已终止
|
||||||
|
|
||||||
|
### 节点状态
|
||||||
|
- `waiting` - 等待审批
|
||||||
|
- `approved` - 已通过
|
||||||
|
- `rejected` - 已拒绝
|
||||||
|
- `pending` - 待处理
|
||||||
|
|
||||||
|
### 审批动作
|
||||||
|
- `approve` - 审批通过
|
||||||
|
- `reject` - 打回申请
|
||||||
|
|
||||||
|
## 技术实现
|
||||||
|
|
||||||
|
- 基于Vue 3 Composition API
|
||||||
|
- 使用Element Plus UI组件库
|
||||||
|
- 集成项目标准的权限控制和国际化
|
||||||
|
- 遵循项目统一的代码规范和样式风格
|
||||||
|
- 参考后勤管理模块的设计风格和布局
|
||||||
|
|
||||||
|
## 数据流程
|
||||||
|
|
||||||
|
1. **流程启动**: 项目申报提交后,根据配置的审核人启动审批流程
|
||||||
|
2. **节点推进**: 当前节点审批完成后,自动推进到下一节点
|
||||||
|
3. **流程完结**: 所有节点审批完成或被打回时,流程结束
|
||||||
|
4. **状态同步**: 审批状态实时同步到项目申报主表
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
### 审批人员操作流程
|
||||||
|
1. 登录系统,进入项目审批管理页面
|
||||||
|
2. 点击"待我审批"标签页查看待处理任务
|
||||||
|
3. 点击具体记录查看申报详情
|
||||||
|
4. 选择审批动作(通过/打回)并填写审批意见
|
||||||
|
5. 提交审批结果,系统自动推进流程
|
||||||
|
|
||||||
|
### 管理员操作流程
|
||||||
|
1. 在"审批流程管理"标签页查看所有流程状态
|
||||||
|
2. 可以终止异常或不需要的审批流程
|
||||||
|
3. 通过统计卡片监控审批效率和积压情况
|
||||||
|
4. 导出审批数据进行分析和存档
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 审批意见为必填项,且至少5个字符
|
||||||
|
2. 审批操作不可撤销,请谨慎操作
|
||||||
|
3. 终止流程会影响项目申报状态,需谨慎使用
|
||||||
|
4. 导出功能需要相应权限,请联系管理员配置
|
||||||
@@ -0,0 +1,462 @@
|
|||||||
|
<template>
|
||||||
|
<div class="approval-process-selector">
|
||||||
|
<el-form-item label="审批流程" required>
|
||||||
|
<el-radio-group v-model="processType" @change="handleProcessTypeChange">
|
||||||
|
<el-radio label="custom">自定义审批流程</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- 自定义审批流程配置 -->
|
||||||
|
<div v-if="processType === 'custom'" class="custom-process-config">
|
||||||
|
<el-form-item label="选择审核人" required>
|
||||||
|
<div class="auditor-selector">
|
||||||
|
<div class="selector-header">
|
||||||
|
<el-button type="primary" icon="Plus" @click="showAuditorSelect">添加审核人</el-button>
|
||||||
|
<el-text v-if="selectedAuditors.length > 0" type="info" size="small">
|
||||||
|
已选择 {{ selectedAuditors.length }} 个审核人,将按添加顺序进行审批
|
||||||
|
</el-text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 已选择的审核人列表 -->
|
||||||
|
<div v-if="selectedAuditors.length > 0" class="selected-auditors">
|
||||||
|
<div class="auditor-flow">
|
||||||
|
<div v-for="(auditor, index) in selectedAuditors" :key="auditor.userId" class="auditor-node">
|
||||||
|
<div class="node-content">
|
||||||
|
<span class="node-index">{{ index + 1 }}</span>
|
||||||
|
<div class="auditor-info">
|
||||||
|
<div class="auditor-name">{{ auditor.userName }}</div>
|
||||||
|
<div class="auditor-dept">{{ auditor.deptName }}</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="danger" icon="Delete" size="small" circle @click="removeAuditor(index)" :disabled="disabled" />
|
||||||
|
</div>
|
||||||
|
<div v-if="index < selectedAuditors.length - 1" class="flow-arrow">
|
||||||
|
<el-icon><ArrowRight /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 流程预览说明 -->
|
||||||
|
<div class="flow-description">
|
||||||
|
<el-alert type="info" :closable="false" show-icon>
|
||||||
|
<template #title>
|
||||||
|
<span>审批流程预览:项目提交后将按上述顺序进行逐级审批,每个节点审批通过后自动流转到下一节点</span>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态提示 -->
|
||||||
|
<div v-else class="empty-state">
|
||||||
|
<el-empty description="暂未选择审核人">
|
||||||
|
<el-button type="primary" @click="showAuditorSelect" :disabled="disabled">选择审核人</el-button>
|
||||||
|
</el-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 审核人选择对话框 -->
|
||||||
|
<el-dialog title="选择审核人" v-model="auditorSelectDialog.visible" width="70%" append-to-body destroy-on-close :close-on-click-modal="false">
|
||||||
|
<div class="auditor-select-content">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<div class="search-section">
|
||||||
|
<el-input v-model="searchKeyword" placeholder="搜索审核人姓名或部门" clearable @input="handleSearch" style="width: 300px">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon><Search /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 审核人表格 -->
|
||||||
|
<el-table
|
||||||
|
ref="auditorTableRef"
|
||||||
|
:data="filteredAuditors"
|
||||||
|
@selection-change="handleAuditorSelectionChange"
|
||||||
|
max-height="400"
|
||||||
|
empty-text="暂无可用审核人"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" :selectable="isAuditorSelectable" />
|
||||||
|
<el-table-column label="序号" type="index" width="60" />
|
||||||
|
<el-table-column label="姓名" prop="userName" width="100" />
|
||||||
|
<el-table-column label="部门" prop="deptName" />
|
||||||
|
<el-table-column label="联系电话" prop="phoneNumber" width="130" />
|
||||||
|
<el-table-column label="邮箱" prop="email" show-overflow-tooltip />
|
||||||
|
<el-table-column label="状态" prop="status" width="80" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="scope.row.status === '1' ? 'success' : 'danger'" size="small">
|
||||||
|
{{ scope.row.status === '1' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-text type="info" size="small"> 已选择 {{ auditorSelectDialog.selectedRows.length }} 个审核人 </el-text>
|
||||||
|
<div class="footer-buttons">
|
||||||
|
<el-button @click="auditorSelectDialog.visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmAuditorSelect" :disabled="auditorSelectDialog.selectedRows.length === 0"> 确定添加 </el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { listAvailableAuditors } from '@/api/cw/projectAuditor';
|
||||||
|
import type { CwProjectAuditorVO } from '@/api/cw/projectAuditor';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: any[];
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: any[]): void;
|
||||||
|
(e: 'change', value: any[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
disabled: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
const processType = ref('custom');
|
||||||
|
const selectedAuditors = ref<CwProjectAuditorVO[]>(props.modelValue || []);
|
||||||
|
const availableAuditors = ref<CwProjectAuditorVO[]>([]);
|
||||||
|
const searchKeyword = ref('');
|
||||||
|
|
||||||
|
// 审核人选择对话框
|
||||||
|
const auditorSelectDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
selectedRows: [] as CwProjectAuditorVO[]
|
||||||
|
});
|
||||||
|
|
||||||
|
const auditorTableRef = ref();
|
||||||
|
|
||||||
|
// 过滤后的审核人列表
|
||||||
|
const filteredAuditors = computed(() => {
|
||||||
|
if (!searchKeyword.value) {
|
||||||
|
return availableAuditors.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyword = searchKeyword.value.toLowerCase();
|
||||||
|
return availableAuditors.value.filter(
|
||||||
|
(auditor) => auditor.userName.toLowerCase().includes(keyword) || auditor.deptName.toLowerCase().includes(keyword)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听选中审核人变化
|
||||||
|
watch(
|
||||||
|
selectedAuditors,
|
||||||
|
(newValue) => {
|
||||||
|
emit('update:modelValue', newValue);
|
||||||
|
emit('change', newValue);
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听外部值变化
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newValue) => {
|
||||||
|
selectedAuditors.value = newValue || [];
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 流程类型变化处理
|
||||||
|
const handleProcessTypeChange = () => {
|
||||||
|
// 预留扩展,当前只支持自定义流程
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示审核人选择对话框
|
||||||
|
const showAuditorSelect = async () => {
|
||||||
|
if (props.disabled) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await listAvailableAuditors();
|
||||||
|
availableAuditors.value = data || [];
|
||||||
|
searchKeyword.value = '';
|
||||||
|
auditorSelectDialog.visible = true;
|
||||||
|
auditorSelectDialog.selectedRows = [];
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('加载审核人列表失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断审核人是否可选择(避免重复选择)
|
||||||
|
const isAuditorSelectable = (auditor: CwProjectAuditorVO) => {
|
||||||
|
return !selectedAuditors.value.some((selected) => selected.userId === auditor.userId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 审核人选择变化
|
||||||
|
const handleAuditorSelectionChange = (selection: CwProjectAuditorVO[]) => {
|
||||||
|
auditorSelectDialog.selectedRows = selection;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索处理
|
||||||
|
const handleSearch = () => {
|
||||||
|
// 搜索时清空选择
|
||||||
|
if (auditorTableRef.value) {
|
||||||
|
auditorTableRef.value.clearSelection();
|
||||||
|
}
|
||||||
|
auditorSelectDialog.selectedRows = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认选择审核人
|
||||||
|
const confirmAuditorSelect = () => {
|
||||||
|
if (auditorSelectDialog.selectedRows.length === 0) {
|
||||||
|
ElMessage.warning('请选择审核人');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤掉已存在的审核人
|
||||||
|
const newAuditors = auditorSelectDialog.selectedRows.filter(
|
||||||
|
(auditor) => !selectedAuditors.value.some((selected) => selected.userId === auditor.userId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newAuditors.length === 0) {
|
||||||
|
ElMessage.warning('所选审核人已存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到已选列表
|
||||||
|
selectedAuditors.value.push(...newAuditors);
|
||||||
|
|
||||||
|
auditorSelectDialog.visible = false;
|
||||||
|
ElMessage.success(`已添加 ${newAuditors.length} 个审核人`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除审核人
|
||||||
|
const removeAuditor = (index: number) => {
|
||||||
|
if (props.disabled) return;
|
||||||
|
|
||||||
|
selectedAuditors.value.splice(index, 1);
|
||||||
|
ElMessage.success('已移除审核人');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 验证审批流程
|
||||||
|
const validateProcess = () => {
|
||||||
|
if (processType.value === 'custom') {
|
||||||
|
if (selectedAuditors.value.length === 0) {
|
||||||
|
return { valid: false, message: '请选择审核人' };
|
||||||
|
}
|
||||||
|
if (selectedAuditors.value.length > 10) {
|
||||||
|
return { valid: false, message: '审核人数量不能超过10个' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { valid: true, message: '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取审批流程配置
|
||||||
|
const getProcessConfig = () => {
|
||||||
|
return {
|
||||||
|
processType: processType.value,
|
||||||
|
auditorIds: selectedAuditors.value.map((auditor) => auditor.userId),
|
||||||
|
processName: `${selectedAuditors.value.map((a) => a.userName).join('→')}审批流程`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义组件名称
|
||||||
|
defineOptions({
|
||||||
|
name: 'ApprovalProcessSelector'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 暴露方法供父组件调用
|
||||||
|
defineExpose({
|
||||||
|
validateProcess,
|
||||||
|
getProcessConfig
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件挂载时加载数据
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.modelValue && props.modelValue.length > 0) {
|
||||||
|
selectedAuditors.value = props.modelValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.approval-process-selector {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-process-config {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 24px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #fafbfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auditor-selector {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-auditors {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auditor-flow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auditor-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: white;
|
||||||
|
border: 2px solid #409eff;
|
||||||
|
border-radius: 8px;
|
||||||
|
gap: 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-content:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-index {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
background: #409eff;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auditor-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auditor-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2329;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auditor-dept {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #86909c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-arrow {
|
||||||
|
color: #409eff;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-description {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对话框内容样式 */
|
||||||
|
.auditor-select-content {
|
||||||
|
max-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对话框底部样式 */
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.auditor-flow {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auditor-node {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-arrow {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深色模式兼容 */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.custom-process-config {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
border-color: #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auditor-flow {
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-color: #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-content {
|
||||||
|
background: #1a1a1a;
|
||||||
|
border-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auditor-name {
|
||||||
|
color: #e5e5e5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,433 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-2 page-container">
|
||||||
|
<!-- 页面头部 -->
|
||||||
|
<el-card shadow="never" class="header-card">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="header-info">
|
||||||
|
<h2 class="page-title">
|
||||||
|
<el-icon><Memo /></el-icon>
|
||||||
|
我的待审批
|
||||||
|
</h2>
|
||||||
|
<p class="page-desc">查看和处理需要我审批的项目申报</p>
|
||||||
|
</div>
|
||||||
|
<div class="header-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number">{{ pendingCount }}</div>
|
||||||
|
<div class="stat-label">待审批</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<el-card shadow="never" class="table-card">
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="pendingList"
|
||||||
|
@row-dblclick="handleView"
|
||||||
|
>
|
||||||
|
<el-table-column label="序号" type="index" width="80" align="center" />
|
||||||
|
<el-table-column label="申报编号" prop="applicationNo" width="160" />
|
||||||
|
<el-table-column label="项目名称" prop="projectName" show-overflow-tooltip />
|
||||||
|
<el-table-column label="申请人" prop="applicantName" width="120" />
|
||||||
|
<el-table-column label="总投资(万元)" prop="totalInvestment" width="130" align="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<span class="amount-text">{{ formatAmount(scope.row.totalInvestment) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="审批节点" prop="customNodeName" width="120" />
|
||||||
|
<el-table-column label="提交时间" prop="startTime" width="180" />
|
||||||
|
<el-table-column label="状态" prop="nodeStatus" width="100" align="center">
|
||||||
|
<template #default>
|
||||||
|
<el-tag type="warning">等待审批</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['cw:projectApproval:query']">查看详情</el-button>
|
||||||
|
<el-button link type="success" icon="Check" @click="handleApprove(scope.row)" v-hasPermi="['cw:projectApproval:approve']">审批</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页组件 -->
|
||||||
|
<el-pagination
|
||||||
|
v-show="total > 0"
|
||||||
|
:total="total"
|
||||||
|
:current-page="queryParams.pageNum"
|
||||||
|
:page-size="queryParams.pageSize"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 审批操作对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
title="审批操作"
|
||||||
|
v-model="approvalDialog.visible"
|
||||||
|
width="600px"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<div class="approval-content">
|
||||||
|
<!-- 项目信息 -->
|
||||||
|
<div class="project-info">
|
||||||
|
<h4>项目信息</h4>
|
||||||
|
<el-descriptions :column="2" border>
|
||||||
|
<el-descriptions-item label="申报编号">{{ currentRecord.applicationNo }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="项目名称">{{ currentRecord.projectName }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="申请人">{{ currentRecord.applicantName }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="总投资">{{ formatAmount(currentRecord.totalInvestment) }} 万元</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 审批表单 -->
|
||||||
|
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="100px">
|
||||||
|
<el-form-item label="审批操作" prop="action">
|
||||||
|
<el-radio-group v-model="approvalForm.action">
|
||||||
|
<el-radio label="approve">
|
||||||
|
<el-icon><Check /></el-icon>
|
||||||
|
审批通过
|
||||||
|
</el-radio>
|
||||||
|
<el-radio label="reject">
|
||||||
|
<el-icon><Close /></el-icon>
|
||||||
|
审批拒绝
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="审批意见" prop="opinion">
|
||||||
|
<el-input
|
||||||
|
v-model="approvalForm.opinion"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="请输入审批意见"
|
||||||
|
maxlength="500"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="approvalDialog.visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitApproval" :loading="approvalDialog.submitting">
|
||||||
|
确认审批
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 详情查看对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
title="项目申报详情"
|
||||||
|
v-model="detailDialog.visible"
|
||||||
|
width="1200px"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
top="20px"
|
||||||
|
>
|
||||||
|
<div v-loading="detailDialog.loading">
|
||||||
|
<ApplicationSummary
|
||||||
|
v-if="detailDialog.visible && currentApplicationId"
|
||||||
|
:applicationId="currentApplicationId"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="detailDialog.visible = false">关闭</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="CwProjectApprovalPending">
|
||||||
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
import { Check, Close, Memo, View } from '@element-plus/icons-vue';
|
||||||
|
import { getMyPendingApprovals, approveProcess, rejectProcess } from '@/api/cw/projectApproval';
|
||||||
|
import ApplicationSummary from '@/views/cw/application/summary.vue';
|
||||||
|
|
||||||
|
// 数据结构用注释说明即可
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const pendingList = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const total = ref(0);
|
||||||
|
const pendingCount = ref(0);
|
||||||
|
|
||||||
|
// 当前审批记录
|
||||||
|
const currentRecord = ref({});
|
||||||
|
|
||||||
|
// 审批对话框
|
||||||
|
const approvalDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
submitting: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 详情对话框
|
||||||
|
const detailDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当前查看的申请ID
|
||||||
|
const currentApplicationId = ref('');
|
||||||
|
|
||||||
|
// 审批表单
|
||||||
|
const approvalForm = reactive({
|
||||||
|
action: 'approve',
|
||||||
|
opinion: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const approvalFormRef = ref();
|
||||||
|
|
||||||
|
// 审批表单验证规则
|
||||||
|
const approvalRules = {
|
||||||
|
action: [{ required: true, message: '请选择审批操作', trigger: 'change' }],
|
||||||
|
opinion: [{ required: true, message: '请输入审批意见', trigger: 'blur' }]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查询待审批列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await getMyPendingApprovals(queryParams);
|
||||||
|
console.log('API响应数据:', response);
|
||||||
|
|
||||||
|
// 后端返回的是R<List<CwApprovalRecordVo>>格式
|
||||||
|
if (response.code === 200 && response.data) {
|
||||||
|
// R格式:{ code: 200, data: [...], msg: 'success' }
|
||||||
|
pendingList.value = response.data || [];
|
||||||
|
total.value = response.data.length || 0;
|
||||||
|
pendingCount.value = response.data.length || 0;
|
||||||
|
} else if (Array.isArray(response)) {
|
||||||
|
// 直接数组格式
|
||||||
|
pendingList.value = response;
|
||||||
|
total.value = response.length;
|
||||||
|
pendingCount.value = response.length;
|
||||||
|
} else if (response.rows) {
|
||||||
|
// TableDataInfo格式
|
||||||
|
pendingList.value = response.rows || [];
|
||||||
|
total.value = response.total || 0;
|
||||||
|
pendingCount.value = response.total || 0;
|
||||||
|
} else {
|
||||||
|
// 默认情况
|
||||||
|
pendingList.value = [];
|
||||||
|
total.value = 0;
|
||||||
|
pendingCount.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('解析后的数据:', {
|
||||||
|
count: pendingList.value.length,
|
||||||
|
data: pendingList.value
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载待审批列表失败:', error);
|
||||||
|
ElMessage.error('加载待审批列表失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleView = (row) => {
|
||||||
|
console.log('查看详情:', row);
|
||||||
|
if (!row.applicationId) {
|
||||||
|
ElMessage.warning('申请ID不存在,无法查看详情');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentApplicationId.value = row.applicationId;
|
||||||
|
detailDialog.visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 审批操作
|
||||||
|
const handleApprove = (row) => {
|
||||||
|
currentRecord.value = row;
|
||||||
|
approvalForm.action = 'approve';
|
||||||
|
approvalForm.opinion = '';
|
||||||
|
approvalDialog.visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交审批
|
||||||
|
const submitApproval = async () => {
|
||||||
|
if (!approvalFormRef.value) return;
|
||||||
|
|
||||||
|
await approvalFormRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
try {
|
||||||
|
approvalDialog.submitting = true;
|
||||||
|
|
||||||
|
const approvalData = {
|
||||||
|
recordId: currentRecord.value.id,
|
||||||
|
action: approvalForm.action,
|
||||||
|
opinion: approvalForm.opinion
|
||||||
|
};
|
||||||
|
|
||||||
|
// 统一使用 approveProcess 接口(后端会根据 action 参数处理)
|
||||||
|
await approveProcess(approvalData);
|
||||||
|
|
||||||
|
ElMessage.success('审批操作成功');
|
||||||
|
approvalDialog.visible = false;
|
||||||
|
|
||||||
|
// 重新加载列表
|
||||||
|
getList();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('审批操作失败');
|
||||||
|
} finally {
|
||||||
|
approvalDialog.submitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化金额
|
||||||
|
const formatAmount = (amount) => {
|
||||||
|
if (!amount) return '0.00';
|
||||||
|
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页事件处理
|
||||||
|
const handleSizeChange = (size) => {
|
||||||
|
queryParams.pageSize = size;
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCurrentChange = (page) => {
|
||||||
|
queryParams.pageNum = page;
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面挂载
|
||||||
|
onMounted(() => {
|
||||||
|
getList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 页面容器样式 */
|
||||||
|
.page-container {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
min-height: calc(100vh - 84px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片样式 */
|
||||||
|
.header-card,
|
||||||
|
.table-card {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面头部样式 */
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2329;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-desc {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #86909c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: linear-gradient(135deg, #409eff, #66b3ff);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 金额文本样式 */
|
||||||
|
.amount-text {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 审批对话框样式 */
|
||||||
|
.approval-content {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-info {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-info h4 {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
color: #1f2329;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header-content {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-stats {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 深色模式兼容 */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.page-container {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-info {
|
||||||
|
background: #2a2a2a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# 项目审批人批量添加功能Bug修复报告
|
||||||
|
|
||||||
|
## 🐛 问题描述
|
||||||
|
用户反馈:批量添加审批人时,选择用户后点击提交,页面提示添加成功,但后台数据库中没有新增数据。
|
||||||
|
|
||||||
|
## 🔍 问题分析
|
||||||
|
|
||||||
|
### 根本原因
|
||||||
|
1. **前端使用模拟API函数**:`index.vue` 中第354-378行定义了模拟的API函数,而不是真实的API调用
|
||||||
|
2. **缺少正确的API导入**:没有导入真实的 `batchAddProjectAuditor` 等API函数
|
||||||
|
3. **数据格式不匹配**:前端传递的数据格式与后端期望格式存在差异
|
||||||
|
|
||||||
|
### 问题代码
|
||||||
|
```javascript
|
||||||
|
// 错误:使用模拟函数
|
||||||
|
const batchAddProjectAuditor = (data: any) => {
|
||||||
|
return Promise.resolve() // 直接返回成功,不实际调用后端
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 修复方案
|
||||||
|
|
||||||
|
### 1. 导入真实API函数
|
||||||
|
```javascript
|
||||||
|
import {
|
||||||
|
listProjectAuditor,
|
||||||
|
getProjectAuditor,
|
||||||
|
updateProjectAuditor,
|
||||||
|
delProjectAuditor,
|
||||||
|
batchAddProjectAuditor
|
||||||
|
} from '@/api/cw/projectAuditor'
|
||||||
|
import type { CwProjectAuditorVO, CwProjectAuditorForm, CwProjectAuditorQuery, CwProjectAuditorBatchBo } from '@/api/cw/types'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 修复数据类型定义
|
||||||
|
```typescript
|
||||||
|
// 修正数据类型
|
||||||
|
const auditorList = ref<CwProjectAuditorVO[]>([])
|
||||||
|
const queryParams = ref<CwProjectAuditorQuery>({...})
|
||||||
|
const editDialog = reactive({
|
||||||
|
form: {} as CwProjectAuditorForm
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 修复批量添加数据格式
|
||||||
|
```javascript
|
||||||
|
const confirmBatchAdd = async () => {
|
||||||
|
const userIds = batchDialog.selectedUsers.map((user) => user.userId as number)
|
||||||
|
const data: CwProjectAuditorBatchBo = {
|
||||||
|
userIds: userIds,
|
||||||
|
remark: '批量添加'
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await batchAddProjectAuditor(data)
|
||||||
|
// ... 处理响应
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 修复后端数据解析
|
||||||
|
```javascript
|
||||||
|
const response = await listProjectAuditor(queryParams.value)
|
||||||
|
// 后端返回的是TableDataInfo格式
|
||||||
|
auditorList.value = response.data?.rows || []
|
||||||
|
total.value = response.data?.total || 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ 修复内容清单
|
||||||
|
|
||||||
|
### 前端修复
|
||||||
|
- [x] 移除模拟API函数,导入真实API
|
||||||
|
- [x] 修复TypeScript类型定义
|
||||||
|
- [x] 修复批量添加数据格式
|
||||||
|
- [x] 修复后端响应数据解析
|
||||||
|
- [x] 改进错误处理和用户反馈
|
||||||
|
- [x] 修复按钮事件类型问题
|
||||||
|
|
||||||
|
### 后端验证
|
||||||
|
- [x] 确认后端API接口正常工作
|
||||||
|
- [x] 确认数据库表结构正确
|
||||||
|
- [x] 确认权限控制配置
|
||||||
|
|
||||||
|
## 🧪 测试验证
|
||||||
|
|
||||||
|
### 测试用例
|
||||||
|
1. **批量添加功能测试**
|
||||||
|
- 选择部门
|
||||||
|
- 选择多个用户
|
||||||
|
- 点击确认添加
|
||||||
|
- 验证数据库是否成功插入数据
|
||||||
|
|
||||||
|
2. **查询列表功能测试**
|
||||||
|
- 验证API调用正常
|
||||||
|
- 验证数据格式解析正确
|
||||||
|
- 验证分页功能
|
||||||
|
|
||||||
|
3. **错误处理测试**
|
||||||
|
- 测试网络错误情况
|
||||||
|
- 测试用户已存在情况
|
||||||
|
- 测试权限不足情况
|
||||||
|
|
||||||
|
### 测试脚本
|
||||||
|
提供了 `test-api.js` 文件用于功能验证测试。
|
||||||
|
|
||||||
|
## 📋 技术规范遵循
|
||||||
|
|
||||||
|
### 符合项目规范
|
||||||
|
- ✅ **前端开发规范**:符合Vue 3.5.13 + TypeScript + Element Plus架构
|
||||||
|
- ✅ **API接口规范**:使用统一的request工具和错误处理
|
||||||
|
- ✅ **UI设计规范**:继承后勤管理模块的样式风格和颜色主题
|
||||||
|
- ✅ **数据库规范**:使用雪花ID、逻辑删除、多租户等标准字段
|
||||||
|
- ✅ **权限控制**:使用@SaCheckPermission注解控制接口权限
|
||||||
|
|
||||||
|
### 代码质量
|
||||||
|
- ✅ **类型安全**:完整的TypeScript类型定义
|
||||||
|
- ✅ **错误处理**:完善的异常捕获和用户提示
|
||||||
|
- ✅ **用户体验**:优化的加载状态和反馈信息
|
||||||
|
- ✅ **响应式设计**:保持左右布局在不同屏幕下的表现
|
||||||
|
|
||||||
|
## 🚀 部署说明
|
||||||
|
|
||||||
|
### 验证步骤
|
||||||
|
1. 确保后端服务正常运行
|
||||||
|
2. 检查数据库表 `cw_project_auditor` 是否存在
|
||||||
|
3. 验证用户权限配置 `cw:projectAuditor:add`
|
||||||
|
4. 测试前端页面功能
|
||||||
|
|
||||||
|
### 预期结果
|
||||||
|
- 批量添加功能正常工作
|
||||||
|
- 数据成功写入数据库
|
||||||
|
- 页面显示准确的成功/失败提示
|
||||||
|
- 列表数据正确刷新
|
||||||
|
|
||||||
|
## 📝 修复日志
|
||||||
|
|
||||||
|
| 时间 | 修复内容 | 影响范围 |
|
||||||
|
|------|----------|----------|
|
||||||
|
| 2025-01-03 | 修复API调用问题 | 批量添加功能 |
|
||||||
|
| 2025-01-03 | 修复数据格式问题 | 前后端数据传递 |
|
||||||
|
| 2025-01-03 | 修复类型定义问题 | TypeScript类型安全 |
|
||||||
|
| 2025-01-03 | 修复响应解析问题 | 列表查询功能 |
|
||||||
|
|
||||||
|
## 🎯 总结
|
||||||
|
|
||||||
|
本次修复解决了批量添加审批人功能的根本问题,确保:
|
||||||
|
1. **功能正常**:批量添加能够真实地向后端发送请求并保存数据
|
||||||
|
2. **类型安全**:完整的TypeScript类型定义和检查
|
||||||
|
3. **用户体验**:准确的成功/失败提示和数据刷新
|
||||||
|
4. **代码质量**:符合项目开发规范和最佳实践
|
||||||
|
|
||||||
|
修复后的功能完全符合需求分析文档的要求,并遵循项目的前后端开发规范。
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# 项目审批人配置模块
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
项目审批人配置模块用于管理项目申报审批流程中的审批人员池,支持从系统用户中选择并配置审批人员,为自定义审批流程提供人员支持。
|
||||||
|
|
||||||
|
## 主要功能
|
||||||
|
|
||||||
|
### 1. 审批人列表管理
|
||||||
|
- 查询审批人列表(支持分页)
|
||||||
|
- 按用户姓名、部门、状态等条件筛选
|
||||||
|
- 显示审批人基本信息(姓名、部门、联系方式等)
|
||||||
|
- 支持审批人状态管理(正常/停用)
|
||||||
|
|
||||||
|
### 2. 批量添加审批人
|
||||||
|
- 通过部门树选择部门
|
||||||
|
- 展示部门下的可用用户列表
|
||||||
|
- 支持多选用户批量添加为审批人
|
||||||
|
- 自动过滤已存在的审批人
|
||||||
|
|
||||||
|
### 3. 审批人信息维护
|
||||||
|
- 修改审批人联系信息
|
||||||
|
- 更新审批人状态
|
||||||
|
- 添加备注信息
|
||||||
|
- 删除审批人配置
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
web/src/views/cw/projectAuditor/
|
||||||
|
├── index.vue # 主页面组件
|
||||||
|
└── README.md # 功能说明文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## API接口
|
||||||
|
|
||||||
|
使用的主要API接口:
|
||||||
|
- `listProjectAuditor` - 查询审批人列表
|
||||||
|
- `getProjectAuditor` - 获取审批人详情
|
||||||
|
- `updateProjectAuditor` - 更新审批人信息
|
||||||
|
- `delProjectAuditor` - 删除审批人
|
||||||
|
- `batchAddProjectAuditor` - 批量添加审批人
|
||||||
|
- `getDeptUsers` - 获取部门用户列表
|
||||||
|
|
||||||
|
## 权限控制
|
||||||
|
|
||||||
|
页面功能对应的权限标识:
|
||||||
|
- `cw:projectAuditor:query` - 查询权限
|
||||||
|
- `cw:projectAuditor:add` - 新增权限
|
||||||
|
- `cw:projectAuditor:edit` - 编辑权限
|
||||||
|
- `cw:projectAuditor:remove` - 删除权限
|
||||||
|
|
||||||
|
## 技术实现
|
||||||
|
|
||||||
|
- 基于Vue 3 Composition API
|
||||||
|
- 使用Element Plus UI组件库
|
||||||
|
- 集成项目标准的权限控制和国际化
|
||||||
|
- 遵循项目统一的代码规范和样式风格
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div class="debug-container">
|
||||||
|
<h3>API调试页面</h3>
|
||||||
|
|
||||||
|
<el-card>
|
||||||
|
<template #header>
|
||||||
|
<span>测试审核人列表API</span>
|
||||||
|
<el-button @click="testAuditorList" type="primary" style="float: right;">测试API</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-if="loading">加载中...</div>
|
||||||
|
|
||||||
|
<div v-if="apiResponse">
|
||||||
|
<h4>原始API响应:</h4>
|
||||||
|
<pre>{{ JSON.stringify(apiResponse, null, 2) }}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="processedData">
|
||||||
|
<h4>处理后的数据:</h4>
|
||||||
|
<pre>{{ JSON.stringify(processedData, null, 2) }}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="auditorList.length > 0">
|
||||||
|
<h4>审核人列表 ({{ auditorList.length }}条):</h4>
|
||||||
|
<el-table :data="auditorList" border>
|
||||||
|
<el-table-column prop="id" label="ID" width="80" />
|
||||||
|
<el-table-column prop="userName" label="用户名" />
|
||||||
|
<el-table-column prop="nickName" label="昵称" />
|
||||||
|
<el-table-column prop="deptName" label="部门" />
|
||||||
|
<el-table-column prop="status" label="状态" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card style="margin-top: 20px;">
|
||||||
|
<template #header>
|
||||||
|
<span>测试部门数据API</span>
|
||||||
|
<el-button @click="testDeptList" type="success" style="float: right;">测试部门API</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-if="deptLoading">加载中...</div>
|
||||||
|
|
||||||
|
<div v-if="deptResponse">
|
||||||
|
<h4>部门API响应:</h4>
|
||||||
|
<pre>{{ JSON.stringify(deptResponse, null, 2) }}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="deptList.length > 0">
|
||||||
|
<h4>部门列表 ({{ deptList.length }}条):</h4>
|
||||||
|
<el-tree :data="deptList" :props="{ label: 'label', children: 'children' }" />
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { listProjectAuditor } from '@/api/cw/projectAuditor';
|
||||||
|
import { listDeptOptions } from '@/api/system/dept';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const apiResponse = ref(null);
|
||||||
|
const processedData = ref(null);
|
||||||
|
const auditorList = ref([]);
|
||||||
|
|
||||||
|
const deptLoading = ref(false);
|
||||||
|
const deptResponse = ref(null);
|
||||||
|
const deptList = ref([]);
|
||||||
|
|
||||||
|
const testAuditorList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const queryParams = {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await listProjectAuditor(queryParams);
|
||||||
|
apiResponse.value = response;
|
||||||
|
console.log('审核人API原始响应:', response);
|
||||||
|
|
||||||
|
// 处理数据结构
|
||||||
|
let responseData;
|
||||||
|
if (response.data) {
|
||||||
|
responseData = response.data;
|
||||||
|
} else {
|
||||||
|
responseData = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
processedData.value = responseData;
|
||||||
|
|
||||||
|
// 尝试不同的数据结构
|
||||||
|
if (responseData.rows && Array.isArray(responseData.rows)) {
|
||||||
|
auditorList.value = responseData.rows;
|
||||||
|
} else if (Array.isArray(responseData)) {
|
||||||
|
auditorList.value = responseData;
|
||||||
|
} else if (responseData.data && Array.isArray(responseData.data)) {
|
||||||
|
auditorList.value = responseData.data;
|
||||||
|
} else {
|
||||||
|
auditorList.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('处理后的审核人列表:', auditorList.value);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('测试审核人API失败:', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const testDeptList = async () => {
|
||||||
|
deptLoading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await listDeptOptions();
|
||||||
|
deptResponse.value = response;
|
||||||
|
console.log('部门API原始响应:', response);
|
||||||
|
|
||||||
|
// 处理数据结构
|
||||||
|
let deptData;
|
||||||
|
if (response.data) {
|
||||||
|
deptData = response.data;
|
||||||
|
} else {
|
||||||
|
deptData = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
deptList.value = Array.isArray(deptData) ? deptData : [];
|
||||||
|
console.log('处理后的部门列表:', deptList.value);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('测试部门API失败:', error);
|
||||||
|
} finally {
|
||||||
|
deptLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.debug-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,952 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-2 page-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||||
|
<div v-show="showSearch" class="mb-[10px]">
|
||||||
|
<el-card shadow="hover">
|
||||||
|
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||||
|
<!-- 第一行:4个搜索条件 -->
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="用户姓名" prop="userName">
|
||||||
|
<el-input v-model="queryParams.userName" placeholder="请输入用户姓名" clearable @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="用户昵称" prop="nickName">
|
||||||
|
<el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="所属部门" prop="deptId">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="queryParams.deptId"
|
||||||
|
:data="deptOptions"
|
||||||
|
:props="{ value: 'id', label: 'label', children: 'children' }"
|
||||||
|
value-key="id"
|
||||||
|
placeholder="请选择部门"
|
||||||
|
check-strictly
|
||||||
|
:render-after-expand="false"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 100%">
|
||||||
|
<el-option label="正常" value="1" />
|
||||||
|
<el-option label="停用" value="0" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 第二行:配置人和时间筛选 + 操作按钮 -->
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="配置人" prop="configBy">
|
||||||
|
<el-input v-model="queryParams.configBy" placeholder="请输入配置人姓名" clearable @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="配置时间">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="daterangeCreateTime"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="-"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
@change="handleQuery"
|
||||||
|
style="width: 100%"
|
||||||
|
></el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<!-- 留空,保持布局对齐 -->
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 操作按钮行 -->
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24" class="search-btn-container">
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<el-card shadow="never" class="button-card table-card">
|
||||||
|
<template #header>
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" plain icon="Plus" @click="handleBatchAdd" v-hasPermi="['cw:projectAuditor:add']">批量添加审核人</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['cw:projectAuditor:edit']">修改</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['cw:projectAuditor:remove']">删除</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button icon="CircleCheck" :disabled="multiple" @click="handleEnable()" v-hasPermi="['cw:projectAuditor:edit']">启用</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button icon="CircleClose" :disabled="multiple" @click="handleDisable()" v-hasPermi="['cw:projectAuditor:edit']">停用</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button icon="Download" @click="handleExport" v-hasPermi="['cw:projectAuditor:export']">导出</el-button>
|
||||||
|
</el-col>
|
||||||
|
<right-toolbar :show-search="showSearch" @update:show-search="(val) => showSearch = val" @queryTable="getList"></right-toolbar>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
border
|
||||||
|
:data="auditorList"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
@row-dblclick="handleDetail"
|
||||||
|
style="width: 100%"
|
||||||
|
:scroll-x="true"
|
||||||
|
class="scroll-table"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" align="center" fixed="left" />
|
||||||
|
<el-table-column label="序号" type="index" width="80" align="center" fixed="left" />
|
||||||
|
<el-table-column label="用户姓名" align="center" prop="userName" width="120" show-overflow-tooltip fixed="left" />
|
||||||
|
<el-table-column label="用户昵称" align="center" prop="nickName" min-width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column label="所属部门" align="center" prop="deptName" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column label="联系电话" align="center" prop="phoneNumber" width="130" show-overflow-tooltip />
|
||||||
|
<el-table-column label="邮箱地址" align="center" prop="email" min-width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="scope.row.status === '1' ? 'success' : 'danger'" size="small">
|
||||||
|
{{ scope.row.status === '1' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="配置人" align="center" prop="configByName" width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column label="配置时间" align="center" prop="createTime" width="180">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ proxy.parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="更新时间" align="center" prop="updateTime" width="180">
|
||||||
|
<template #default="scope">
|
||||||
|
<span v-if="scope.row.updateTime">{{ proxy.parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注说明" align="center" prop="remark" min-width="200" show-overflow-tooltip>
|
||||||
|
<template #default="scope">
|
||||||
|
<span v-if="scope.row.remark">{{ scope.row.remark }}</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tooltip content="查看详情" placement="top">
|
||||||
|
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)" v-hasPermi="['cw:projectAuditor:query']"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="修改信息" placement="top">
|
||||||
|
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['cw:projectAuditor:edit']"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="删除审核人" placement="top">
|
||||||
|
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['cw:projectAuditor:remove']"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip v-if="scope.row.status === '1'" content="停用" placement="top">
|
||||||
|
<el-button link type="warning" icon="CircleClose" @click="handleDisable(scope.row)" v-hasPermi="['cw:projectAuditor:edit']"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip v-else content="启用" placement="top">
|
||||||
|
<el-button link type="success" icon="CircleCheck" @click="handleEnable(scope.row)" v-hasPermi="['cw:projectAuditor:edit']"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页组件 -->
|
||||||
|
<pagination
|
||||||
|
v-show="total > 0"
|
||||||
|
:total="total"
|
||||||
|
:page="queryParams.pageNum"
|
||||||
|
:limit="queryParams.pageSize"
|
||||||
|
@update:page="(val) => queryParams.pageNum = val"
|
||||||
|
@update:limit="(val) => queryParams.pageSize = val"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 批量添加审核人对话框 -->
|
||||||
|
<el-dialog title="批量添加审核人" v-model="batchAddDialog.visible" width="80%" append-to-body>
|
||||||
|
<div class="batch-add-container">
|
||||||
|
<!-- 左侧部门选择 -->
|
||||||
|
<div class="dept-select-section">
|
||||||
|
<el-card shadow="never" class="dept-tree-card">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>选择部门</span>
|
||||||
|
<el-button link @click="expandAllDept">
|
||||||
|
<el-icon><Operation /></el-icon>
|
||||||
|
{{ deptExpanded ? '收起' : '展开' }}全部
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-tree
|
||||||
|
ref="deptTreeRef"
|
||||||
|
:data="deptTreeData"
|
||||||
|
:props="{ label: 'label', children: 'children' }"
|
||||||
|
node-key="id"
|
||||||
|
@node-click="handleDeptClick"
|
||||||
|
:default-expand-all="deptExpanded"
|
||||||
|
:expand-on-click-node="false"
|
||||||
|
highlight-current
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧用户选择 -->
|
||||||
|
<div class="user-select-section">
|
||||||
|
<el-card shadow="never" class="user-table-card">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>部门用户列表</span>
|
||||||
|
<el-tag v-if="currentDeptName" type="info" size="small">{{ currentDeptName }}</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-table
|
||||||
|
ref="userTableRef"
|
||||||
|
v-loading="userLoading"
|
||||||
|
:data="deptUserList"
|
||||||
|
@selection-change="handleUserSelectionChange"
|
||||||
|
max-height="400"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column label="姓名" prop="nickName" width="100" />
|
||||||
|
<el-table-column label="用户名" prop="userName" width="120" />
|
||||||
|
<el-table-column label="部门" prop="deptName" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column label="手机号" prop="phonenumber" width="130" />
|
||||||
|
<el-table-column label="邮箱" prop="email" min-width="180" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 已选择用户预览 -->
|
||||||
|
<el-card v-if="batchAddDialog.selectedUsers.length > 0" shadow="never" class="selected-users-card">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>已选择用户 ({{ batchAddDialog.selectedUsers.length }})</span>
|
||||||
|
<el-button link @click="clearSelectedUsers">
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
清空选择
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="selected-users-list">
|
||||||
|
<el-tag
|
||||||
|
v-for="user in batchAddDialog.selectedUsers"
|
||||||
|
:key="user.userId"
|
||||||
|
closable
|
||||||
|
@close="removeSelectedUser(user)"
|
||||||
|
class="selected-user-tag"
|
||||||
|
>
|
||||||
|
{{ user.nickName }}({{ user.userName }})
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="batchAddDialog.visible = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="confirmBatchAdd"
|
||||||
|
:disabled="batchAddDialog.selectedUsers.length === 0"
|
||||||
|
:loading="batchAddLoading"
|
||||||
|
>
|
||||||
|
确定添加 ({{ batchAddDialog.selectedUsers.length }})
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 审核人详情对话框 -->
|
||||||
|
<el-dialog title="审核人详情" v-model="detailDialog.visible" width="600px" append-to-body>
|
||||||
|
<el-descriptions v-if="detailDialog.data" :column="2" border>
|
||||||
|
<el-descriptions-item label="用户姓名">{{ detailDialog.data.userName }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="用户昵称">{{ detailDialog.data.nickName }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="所属部门">{{ detailDialog.data.deptName }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="联系电话">{{ detailDialog.data.phoneNumber || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="邮箱地址">{{ detailDialog.data.email || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="状态">
|
||||||
|
<el-tag :type="detailDialog.data.status === '1' ? 'success' : 'danger'">
|
||||||
|
{{ detailDialog.data.status === '1' ? '正常' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="配置人">{{ detailDialog.data.configByName }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="配置时间">{{ proxy.parseTime(detailDialog.data.createTime, '{y}-{m}-{d} {h}:{i}') }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="更新时间" :span="2">
|
||||||
|
{{ detailDialog.data.updateTime ? proxy.parseTime(detailDialog.data.updateTime, '{y}-{m}-{d} {h}:{i}') : '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="备注说明" :span="2">{{ detailDialog.data.remark || '-' }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 编辑审核人对话框 -->
|
||||||
|
<el-dialog title="修改审核人信息" v-model="editDialog.visible" width="600px" append-to-body>
|
||||||
|
<el-form ref="editFormRef" :model="editDialog.form" :rules="editRules" label-width="100px">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="用户姓名" prop="userName">
|
||||||
|
<el-input v-model="editDialog.form.userName" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="用户昵称" prop="nickName">
|
||||||
|
<el-input v-model="editDialog.form.nickName" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="联系电话" prop="phoneNumber">
|
||||||
|
<el-input v-model="editDialog.form.phoneNumber" placeholder="请输入联系电话" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-radio-group v-model="editDialog.form.status">
|
||||||
|
<el-radio label="1">正常</el-radio>
|
||||||
|
<el-radio label="0">停用</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="邮箱地址" prop="email">
|
||||||
|
<el-input v-model="editDialog.form.email" placeholder="请输入邮箱地址" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注说明" prop="remark">
|
||||||
|
<el-input
|
||||||
|
v-model="editDialog.form.remark"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入备注说明"
|
||||||
|
maxlength="500"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="editDialog.visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitEdit" :loading="editLoading">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="CwProjectAuditor">
|
||||||
|
import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue';
|
||||||
|
import { ElMessage, ElMessageBox, ElTable, ElTree, FormInstance } from 'element-plus';
|
||||||
|
import {
|
||||||
|
listProjectAuditor,
|
||||||
|
getProjectAuditor,
|
||||||
|
addProjectAuditor,
|
||||||
|
updateProjectAuditor,
|
||||||
|
delProjectAuditor,
|
||||||
|
batchAddProjectAuditor,
|
||||||
|
listMyConfigAuditors,
|
||||||
|
listUserByDeptId,
|
||||||
|
enableAuditors,
|
||||||
|
disableAuditors,
|
||||||
|
exportProjectAuditor,
|
||||||
|
type CwProjectAuditorVO,
|
||||||
|
type CwProjectAuditorForm,
|
||||||
|
type CwProjectAuditorQuery,
|
||||||
|
type BatchAddRequest,
|
||||||
|
type DeptUserVO
|
||||||
|
} from '@/api/cw/projectAuditor';
|
||||||
|
import { listDept } from '@/api/system/dept/index';
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() as any;
|
||||||
|
|
||||||
|
// 表单引用
|
||||||
|
const queryFormRef = ref<FormInstance>();
|
||||||
|
const deptTreeRef = ref<InstanceType<typeof ElTree>>();
|
||||||
|
const userTableRef = ref<InstanceType<typeof ElTable>>();
|
||||||
|
const editFormRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
// 显示搜索条件
|
||||||
|
const showSearch = ref(true);
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive<CwProjectAuditorQuery>({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
userName: '',
|
||||||
|
nickName: '',
|
||||||
|
deptId: undefined,
|
||||||
|
status: '',
|
||||||
|
configBy: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
// 时间范围
|
||||||
|
const daterangeCreateTime = ref<string[]>([]);
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const auditorList = ref<CwProjectAuditorVO[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const total = ref(0);
|
||||||
|
|
||||||
|
// 选择状态
|
||||||
|
const ids = ref<Array<string | number>>([]);
|
||||||
|
const single = ref(true);
|
||||||
|
const multiple = ref(true);
|
||||||
|
|
||||||
|
|
||||||
|
// 部门数据
|
||||||
|
const deptOptions = ref([]);
|
||||||
|
const deptTreeData = ref([]);
|
||||||
|
const deptExpanded = ref(false);
|
||||||
|
|
||||||
|
// 批量添加对话框
|
||||||
|
const batchAddDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
selectedUsers: [] as DeptUserVO[]
|
||||||
|
});
|
||||||
|
|
||||||
|
const batchAddLoading = ref(false);
|
||||||
|
const userLoading = ref(false);
|
||||||
|
const deptUserList = ref<DeptUserVO[]>([]);
|
||||||
|
const currentDeptName = ref('');
|
||||||
|
|
||||||
|
// 详情对话框
|
||||||
|
const detailDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
data: null as CwProjectAuditorVO | null
|
||||||
|
});
|
||||||
|
|
||||||
|
// 编辑对话框
|
||||||
|
const editDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
form: {} as CwProjectAuditorForm
|
||||||
|
});
|
||||||
|
|
||||||
|
const editLoading = ref(false);
|
||||||
|
|
||||||
|
// 编辑表单验证规则
|
||||||
|
const editRules = {
|
||||||
|
phoneNumber: [
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
email: [
|
||||||
|
{ type: 'email' as const, message: '请输入正确的邮箱地址', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
status: [
|
||||||
|
{ required: true, message: '请选择状态', trigger: 'change' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查询审核人列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
// 处理时间范围
|
||||||
|
if (daterangeCreateTime.value && daterangeCreateTime.value.length === 2) {
|
||||||
|
queryParams.createTime = daterangeCreateTime.value;
|
||||||
|
} else {
|
||||||
|
queryParams.createTime = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await listProjectAuditor(queryParams);
|
||||||
|
console.log('API响应数据:', response);
|
||||||
|
|
||||||
|
// 处理不同的响应数据结构
|
||||||
|
let responseData;
|
||||||
|
if (response.data) {
|
||||||
|
responseData = response.data;
|
||||||
|
} else {
|
||||||
|
responseData = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试不同的数据结构
|
||||||
|
if (responseData.rows && Array.isArray(responseData.rows)) {
|
||||||
|
auditorList.value = responseData.rows;
|
||||||
|
total.value = responseData.total || responseData.rows.length;
|
||||||
|
} else if (Array.isArray(responseData)) {
|
||||||
|
auditorList.value = responseData;
|
||||||
|
total.value = responseData.length;
|
||||||
|
} else if (responseData.data && Array.isArray(responseData.data)) {
|
||||||
|
auditorList.value = responseData.data;
|
||||||
|
total.value = responseData.total || responseData.data.length;
|
||||||
|
} else {
|
||||||
|
auditorList.value = [];
|
||||||
|
total.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('处理后的审核人列表:', auditorList.value);
|
||||||
|
console.log('总数:', total.value);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取审核人列表失败:', error);
|
||||||
|
auditorList.value = [];
|
||||||
|
total.value = 0;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNum = 1;
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
daterangeCreateTime.value = [];
|
||||||
|
queryFormRef.value?.resetFields();
|
||||||
|
handleQuery();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 多选框选中数据
|
||||||
|
const handleSelectionChange = (selection: CwProjectAuditorVO[]) => {
|
||||||
|
ids.value = selection.map(item => item.id);
|
||||||
|
single.value = selection.length !== 1;
|
||||||
|
multiple.value = !selection.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleDetail = (row: CwProjectAuditorVO) => {
|
||||||
|
detailDialog.data = row;
|
||||||
|
detailDialog.visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改审核人
|
||||||
|
const handleUpdate = (row?: CwProjectAuditorVO) => {
|
||||||
|
if (row) {
|
||||||
|
editDialog.form = { ...row };
|
||||||
|
} else if (ids.value.length === 1) {
|
||||||
|
const selectedRow = auditorList.value.find(item => item.id === ids.value[0]);
|
||||||
|
if (selectedRow) {
|
||||||
|
editDialog.form = { ...selectedRow };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editDialog.visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交编辑
|
||||||
|
const submitEdit = async () => {
|
||||||
|
if (!editFormRef.value) return;
|
||||||
|
|
||||||
|
const valid = await editFormRef.value.validate().catch(() => false);
|
||||||
|
if (!valid) return;
|
||||||
|
|
||||||
|
editLoading.value = true;
|
||||||
|
try {
|
||||||
|
await updateProjectAuditor(editDialog.form);
|
||||||
|
ElMessage.success('修改成功');
|
||||||
|
editDialog.visible = false;
|
||||||
|
getList();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('修改审核人失败:', error);
|
||||||
|
} finally {
|
||||||
|
editLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除审核人
|
||||||
|
const handleDelete = (row?: CwProjectAuditorVO) => {
|
||||||
|
const deleteIds = row ? [row.id] : ids.value;
|
||||||
|
const names = row ? [row.userName] : auditorList.value
|
||||||
|
.filter((item) => deleteIds.includes(item.id))
|
||||||
|
.map((item) => item.userName);
|
||||||
|
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`是否确认删除审核人"${names.join(', ')}"?`,
|
||||||
|
'警告',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
).then(async () => {
|
||||||
|
try {
|
||||||
|
await delProjectAuditor(deleteIds);
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
getList();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除审核人失败:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 启用审核人
|
||||||
|
const handleEnable = (row?: CwProjectAuditorVO) => {
|
||||||
|
const enableIds = row ? [row.id] : ids.value;
|
||||||
|
|
||||||
|
ElMessageBox.confirm('是否确认启用选中的审核人?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'info'
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
await enableAuditors(enableIds);
|
||||||
|
ElMessage.success('启用成功');
|
||||||
|
getList();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('启用审核人失败:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 停用审核人
|
||||||
|
const handleDisable = (row?: CwProjectAuditorVO) => {
|
||||||
|
const disableIds = row ? [row.id] : ids.value;
|
||||||
|
|
||||||
|
ElMessageBox.confirm('是否确认停用选中的审核人?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
await disableAuditors(disableIds);
|
||||||
|
ElMessage.success('停用成功');
|
||||||
|
getList();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('停用审核人失败:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
const handleExport = () => {
|
||||||
|
proxy.download(
|
||||||
|
'/cw/projectAuditor/export',
|
||||||
|
{
|
||||||
|
...queryParams
|
||||||
|
},
|
||||||
|
`项目审核人配置_${new Date().getTime()}.xlsx`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量添加审核人
|
||||||
|
const handleBatchAdd = () => {
|
||||||
|
batchAddDialog.visible = true;
|
||||||
|
batchAddDialog.selectedUsers = [];
|
||||||
|
loadDeptTree();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载部门树数据
|
||||||
|
const loadDeptTree = async () => {
|
||||||
|
try {
|
||||||
|
const response = await listDept();
|
||||||
|
console.log('部门选项响应:', response);
|
||||||
|
|
||||||
|
let deptData;
|
||||||
|
if (response.data) {
|
||||||
|
deptData = response.data;
|
||||||
|
} else {
|
||||||
|
deptData = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证部门数据结构
|
||||||
|
if (Array.isArray(deptData)) {
|
||||||
|
// 检查数据格式并打印前几个节点用于调试
|
||||||
|
console.log('部门数据示例:', deptData.slice(0, 3));
|
||||||
|
|
||||||
|
// 转换为树形结构,适配 el-tree-select 组件
|
||||||
|
const treeData = proxy?.handleTree(deptData, 'deptId', 'parentId', 'children', '0') || [];
|
||||||
|
|
||||||
|
// 转换数据格式以适配 el-tree-select
|
||||||
|
const formatTreeData = (nodes: any[]): any[] => {
|
||||||
|
return nodes.map(node => ({
|
||||||
|
id: node.deptId,
|
||||||
|
label: node.deptName,
|
||||||
|
value: node.deptId,
|
||||||
|
children: node.children ? formatTreeData(node.children) : []
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedData = formatTreeData(treeData);
|
||||||
|
|
||||||
|
deptOptions.value = formattedData;
|
||||||
|
deptTreeData.value = formattedData;
|
||||||
|
} else {
|
||||||
|
console.warn('部门数据格式错误,期望数组但收到:', typeof deptData);
|
||||||
|
deptOptions.value = [];
|
||||||
|
deptTreeData.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('处理后的部门选项数量:', deptOptions.value.length);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载部门选项失败:', error);
|
||||||
|
ElMessage.error('加载部门数据失败,请刷新页面重试');
|
||||||
|
deptOptions.value = [];
|
||||||
|
deptTreeData.value = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 展开/收起所有部门
|
||||||
|
const expandAllDept = () => {
|
||||||
|
deptExpanded.value = !deptExpanded.value;
|
||||||
|
if (deptTreeRef.value) {
|
||||||
|
const treeInstance = deptTreeRef.value as any;
|
||||||
|
if (deptExpanded.value) {
|
||||||
|
treeInstance.setExpandedKeys(deptTreeData.value.map((item: any) => item.id));
|
||||||
|
} else {
|
||||||
|
treeInstance.setExpandedKeys([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 部门选择处理
|
||||||
|
const handleDeptClick = async (data: any) => {
|
||||||
|
console.log('部门树节点点击:', data);
|
||||||
|
console.log('节点ID类型:', typeof data?.id, '值:', data?.id);
|
||||||
|
|
||||||
|
// 验证deptId是否为有效的数字
|
||||||
|
if (data?.id && !isNaN(Number(data.id)) && data.id !== 'treeselect') {
|
||||||
|
try {
|
||||||
|
userLoading.value = true;
|
||||||
|
currentDeptName.value = data.label || data.deptName;
|
||||||
|
|
||||||
|
// 确保传递数字类型的ID
|
||||||
|
const deptId = Number(data.id);
|
||||||
|
console.log('调用API,部门ID:', deptId);
|
||||||
|
|
||||||
|
const response = await listUserByDeptId(deptId);
|
||||||
|
console.log('部门用户数据响应:', response);
|
||||||
|
|
||||||
|
let userData;
|
||||||
|
if (response.data) {
|
||||||
|
userData = response.data;
|
||||||
|
} else {
|
||||||
|
userData = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
deptUserList.value = Array.isArray(userData) ? userData : [];
|
||||||
|
console.log('处理后的用户数据:', deptUserList.value);
|
||||||
|
|
||||||
|
// 切换部门后,恢复之前选中的用户状态
|
||||||
|
nextTick(() => {
|
||||||
|
if (userTableRef.value) {
|
||||||
|
// 清空选择
|
||||||
|
userTableRef.value.clearSelection();
|
||||||
|
// 恢复已选择的用户
|
||||||
|
deptUserList.value.forEach(user => {
|
||||||
|
const isSelected = batchAddDialog.selectedUsers.some(selected => selected.userId === user.userId);
|
||||||
|
if (isSelected) {
|
||||||
|
userTableRef.value?.toggleRowSelection(user, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取部门用户失败:', error);
|
||||||
|
ElMessage.error('获取部门用户失败,请重试');
|
||||||
|
deptUserList.value = [];
|
||||||
|
} finally {
|
||||||
|
userLoading.value = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('无效的部门节点:', data);
|
||||||
|
ElMessage.warning('请选择有效的部门节点');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 用户选择变化
|
||||||
|
const handleUserSelectionChange = (selection: DeptUserVO[]) => {
|
||||||
|
// 移除当前部门中未选中的用户
|
||||||
|
batchAddDialog.selectedUsers = batchAddDialog.selectedUsers.filter(user =>
|
||||||
|
!deptUserList.value.some(deptUser => deptUser.userId === user.userId)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 添加新选中的用户
|
||||||
|
batchAddDialog.selectedUsers.push(...selection);
|
||||||
|
|
||||||
|
// 去重
|
||||||
|
const userMap = new Map();
|
||||||
|
batchAddDialog.selectedUsers.forEach(user => {
|
||||||
|
userMap.set(user.userId, user);
|
||||||
|
});
|
||||||
|
batchAddDialog.selectedUsers = Array.from(userMap.values());
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除已选择的用户
|
||||||
|
const removeSelectedUser = (user: DeptUserVO) => {
|
||||||
|
const index = batchAddDialog.selectedUsers.findIndex(item => item.userId === user.userId);
|
||||||
|
if (index > -1) {
|
||||||
|
batchAddDialog.selectedUsers.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果该用户在当前显示的部门用户列表中,取消选中状态
|
||||||
|
if (userTableRef.value && deptUserList.value.some(deptUser => deptUser.userId === user.userId)) {
|
||||||
|
const tableUser = deptUserList.value.find(deptUser => deptUser.userId === user.userId);
|
||||||
|
if (tableUser) {
|
||||||
|
userTableRef.value.toggleRowSelection(tableUser, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清空选择
|
||||||
|
const clearSelectedUsers = () => {
|
||||||
|
batchAddDialog.selectedUsers = [];
|
||||||
|
if (userTableRef.value) {
|
||||||
|
userTableRef.value.clearSelection();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认批量添加
|
||||||
|
const confirmBatchAdd = async () => {
|
||||||
|
if (batchAddDialog.selectedUsers.length === 0) {
|
||||||
|
ElMessage.warning('请选择要添加的用户');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
batchAddLoading.value = true;
|
||||||
|
try {
|
||||||
|
const userIds = batchAddDialog.selectedUsers.map(user => user.userId);
|
||||||
|
const request: BatchAddRequest = {
|
||||||
|
userIds,
|
||||||
|
remark: '批量添加的审核人'
|
||||||
|
};
|
||||||
|
|
||||||
|
await batchAddProjectAuditor(request);
|
||||||
|
ElMessage.success(`成功添加 ${batchAddDialog.selectedUsers.length} 个审核人`);
|
||||||
|
batchAddDialog.visible = false;
|
||||||
|
batchAddDialog.selectedUsers = [];
|
||||||
|
getList();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量添加审核人失败:', error);
|
||||||
|
} finally {
|
||||||
|
batchAddLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面挂载
|
||||||
|
onMounted(() => {
|
||||||
|
getList();
|
||||||
|
loadDeptTree();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 页面容器样式 */
|
||||||
|
.page-container {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索按钮容器样式 */
|
||||||
|
.search-btn-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.el-form-item {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格卡片布局 */
|
||||||
|
.table-card {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card :deep(.el-card__body) {
|
||||||
|
padding: 0px 20px 20px 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格主体 */
|
||||||
|
.table-container {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除按钮栏底部边框 */
|
||||||
|
.button-card :deep(.el-card__header) {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 表格样式 */
|
||||||
|
.scroll-table {
|
||||||
|
:deep(.el-table__header-wrapper) {
|
||||||
|
.el-table__header {
|
||||||
|
th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #495057;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 批量添加对话框样式 */
|
||||||
|
.batch-add-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dept-select-section {
|
||||||
|
width: 300px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-select-section {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dept-tree-card,
|
||||||
|
.user-table-card,
|
||||||
|
.selected-users-card {
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-table-card {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-users-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
max-height: 120px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-user-tag {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对话框底部样式 */
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div class="test-container">
|
||||||
|
<h2>项目审核人配置页面测试</h2>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h3>1. 数据初始化测试</h3>
|
||||||
|
<p>部门选项: {{ deptOptions.length }} 个</p>
|
||||||
|
<p>审核人列表: {{ auditorList.length }} 个</p>
|
||||||
|
<p>加载状态: {{ loading ? '加载中...' : '已完成' }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h3>2. API响应测试</h3>
|
||||||
|
<el-button @click="testAPI" type="primary">测试API调用</el-button>
|
||||||
|
<pre>{{ apiResult }}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h3>3. 部门选择测试</h3>
|
||||||
|
<el-tree-select
|
||||||
|
v-model="selectedDept"
|
||||||
|
:data="deptOptions"
|
||||||
|
placeholder="选择部门"
|
||||||
|
clearable
|
||||||
|
style="width: 300px"
|
||||||
|
/>
|
||||||
|
<p>选中部门: {{ selectedDept }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="ProjectAuditorTest">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
// 测试数据
|
||||||
|
const deptOptions = ref<any[]>([]);
|
||||||
|
const auditorList = ref<any[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const selectedDept = ref();
|
||||||
|
const apiResult = ref('');
|
||||||
|
|
||||||
|
// 模拟部门数据
|
||||||
|
const mockDeptOptions = [
|
||||||
|
{ value: '1', label: '总公司', children: [
|
||||||
|
{ value: '2', label: '财务部' },
|
||||||
|
{ value: '3', label: '人事部' }
|
||||||
|
]},
|
||||||
|
{ value: '4', label: '分公司', children: [
|
||||||
|
{ value: '5', label: '技术部' },
|
||||||
|
{ value: '6', label: '市场部' }
|
||||||
|
]}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 模拟API响应数据
|
||||||
|
const mockApiResponse = {
|
||||||
|
code: 200,
|
||||||
|
msg: '操作成功',
|
||||||
|
data: {
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
userId: '1001',
|
||||||
|
userName: '张三',
|
||||||
|
deptName: '财务部',
|
||||||
|
status: '1',
|
||||||
|
createTime: '2025-01-03 10:00:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
userId: '1002',
|
||||||
|
userName: '李四',
|
||||||
|
deptName: '人事部',
|
||||||
|
status: '1',
|
||||||
|
createTime: '2025-01-03 11:00:00'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
total: 2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 测试API调用
|
||||||
|
const testAPI = () => {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
// 模拟异步API调用
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
const { data } = mockApiResponse;
|
||||||
|
auditorList.value = data?.rows || [];
|
||||||
|
apiResult.value = JSON.stringify(data, null, 2);
|
||||||
|
|
||||||
|
ElMessage.success('API调用成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API调用失败:', error);
|
||||||
|
ElMessage.error('API调用失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
const initData = () => {
|
||||||
|
deptOptions.value = mockDeptOptions;
|
||||||
|
ElMessage.info('测试页面初始化完成');
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.test-container {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-section h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in new issue
Block a user