feat: 品牌工作室初始化提交

This commit is contained in:
2026-08-19 09:17:12 +08:00
parent 1096ea245f
commit 1b17c3434b
83 changed files with 10917 additions and 0 deletions
@@ -41,6 +41,8 @@ public enum BpmProcessConstant {
ARTICLE("新闻投稿"),
BRAND_STUDIO("品牌工作室"),
BRANCH_UNION_WEIYUAN_AUTHORIZATION("分工会委员授权")
;
@@ -0,0 +1,43 @@
package com.budwk.app.zhgh.brand.controller;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.brand.domain.dto.BrandApplicationAuditDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandApplicationRecallDTO;
import com.budwk.app.zhgh.brand.domain.vo.BrandApplicationFlowVO;
import com.budwk.app.zhgh.brand.service.BrandApplicationFlowService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import java.util.List;
@IocBean
@Ok("json:full")
@At("/platform/zhgh/brand/flow")
public class BrandApplicationFlowController {
@Inject
private BrandApplicationFlowService flowService;
@At("/process")
public Result process(@Param("applicationId") String applicationId) {
List<BrandApplicationFlowVO> flowList = flowService.getFlowList(applicationId);
return Result.success(flowList);
}
@At("/audit")
public Result audit(@Param("..") @Valid BrandApplicationAuditDTO dto) {
flowService.audit(dto);
return Result.success();
}
@At("/recall")
public Result recall(@Param("..") BrandApplicationRecallDTO dto) {
flowService.recall(dto);
return Result.success();
}
}
@@ -0,0 +1,47 @@
package com.budwk.app.zhgh.brand.controller;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.brand.domain.vo.ApplicationProgressVO;
import com.budwk.app.zhgh.brand.domain.vo.ApplicationWarnVO;
import com.budwk.app.zhgh.brand.domain.vo.UnionApplyStatsVO;
import com.budwk.app.zhgh.brand.service.BrandApplicationStatisticsService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import java.util.List;
import java.util.Map;
@IocBean
@Ok("json:full")
@At("/platform/zhgh/brand/statistics")
public class BrandApplicationStatisticsController {
@Inject
private BrandApplicationStatisticsService statisticsService;
@At("/unionStats")
public Result selectUnionApplyStats() {
List<UnionApplyStatsVO> vos = statisticsService.selectUnionApplyStats();
return Result.success(vos);
}
@At("/applyProgress")
public Result selectApplyProgressStats() {
List<ApplicationProgressVO> vos = statisticsService.selectApplyProgressStats();
return Result.success(vos);
}
@At("/applyWarning")
public Result selectApplyWarningStats() {
List<ApplicationWarnVO> vos = statisticsService.selectApplyWarningStats();
return Result.success(vos);
}
@At("/applyCount")
public Result selectApplyMapCount() {
Map<String, Map<String, Long>> selectApplyMapCount = statisticsService.selectApplyMapCount();
return Result.success(selectApplyMapCount);
}
}
@@ -0,0 +1,118 @@
package com.budwk.app.zhgh.brand.controller;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.brand.convert.BrandStudioApplicationConvert;
import com.budwk.app.zhgh.brand.domain.dto.BrandApplicationAuditDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioApplicationCreateDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioApplicationPageDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioApplicationUpdateDTO;
import com.budwk.app.zhgh.brand.domain.vo.*;
import com.budwk.app.zhgh.brand.models.BrandStudioApplication;
import com.budwk.app.zhgh.brand.service.BrandStudioApplicationService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
@IocBean
@Ok("json:full")
@At("/platform/zhgh/brand/application")
public class BrandStudioApplicationController {
@Inject
private BrandStudioApplicationService applicationService;
@At("/leader")
public Result queryLeaderHonor() {
List<LeaderHonorGroupVO> leaderHonorVO = applicationService.queryLeaderHonor();
return Result.success(leaderHonorVO);
}
@At("/page")
public Result page(@Valid BrandStudioApplicationPageDTO dto) {
return Result.success(applicationService.page(dto));
}
@At("/{id}")
public Result get(@Param("id") String id) {
BrandStudioApplicationVO applicationVO = applicationService.getDetailById(id);
return Result.success(applicationVO);
}
@At
public Result save(@Param("..") @Valid BrandStudioApplicationCreateDTO dto) {
applicationService.save(dto);
return Result.success();
}
@At("/submitApply")
public Result submitApply(@Param("..") @Valid BrandApplicationAuditDTO dto) {
applicationService.submitApply(dto);
return Result.success();
}
@At("/delete")
public Result delete(@Param("..") NutMap request) {
applicationService.delete((List<String>) request.get("data"));
return Result.success();
}
@At("/update")
public Result update(@Param("..") @Valid BrandStudioApplicationUpdateDTO updateDTO) {
applicationService.update(updateDTO);
return Result.success();
}
@At("/base")
public Result updateBase(@Param("..") BrandStudioApplicationUpdateDTO updateDTO) {
BrandStudioApplication entity = BrandStudioApplicationConvert.convert(updateDTO);
applicationService.updateIgnoreNull(entity);
return Result.success();
}
@At("/auditPage")
public Result auditPage(@Valid BrandStudioApplicationPageDTO dto) {
// PageResult<BrandStudioApplyFlowVO> page = applicationService.auditPage(dto);
return Result.success(applicationService.flowAuditPage(dto));
}
@At("/archive")
public Result archive(@Param("ids") List<String> idList) {
applicationService.archive(idList);
return Result.success();
}
@At("/honor/{id}")
public Result honor(@Param("id") String id) {
List<BrandStudioApplicationHonorVO> vos = applicationService.getHonorDetailById(id);
return Result.success(vos);
}
@At("/summaryPage")
public Result summaryPage(@Valid BrandStudioApplicationPageDTO dto) {
return Result.success(applicationService.summaryPage(dto));
}
@At("/honor")
public Result updateMemberOrHonor(@Param("..") @Valid BrandStudioApplicationUpdateDTO updateDTO) {
applicationService.updateMemberOrHonor(updateDTO);
return Result.success();
}
@At("/export")
public void export() {
applicationService.export();
}
@At("/export/docx/{id}")
public void exportDocx(@Param("id") String id, HttpServletResponse response) {
applicationService.exportDocx(id, response);
}
}
@@ -0,0 +1,50 @@
package com.budwk.app.zhgh.brand.controller;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@IocBean
@At("/platform/zhgh/brand")
public class BrandStudioPageController {
/**
* 品牌工作室申请列表页面
*/
@At("/application/index")
@Ok("beetl:/platform/zhgh/brand/application/index.html")
public void applicationIndex() {
}
/**
* 品牌工作室申请填报页面
*/
@At("/apply/index")
@Ok("beetl:/platform/zhgh/brand/apply/index.html")
public void applyIndex() {
}
/**
* 品牌工作室审批页面
*/
@At("/review/index")
@Ok("beetl:/platform/zhgh/brand/review/index.html")
public void reviewIndex() {
}
/**
* 品牌工作室汇总页面
*/
@At("/summary/index")
@Ok("beetl:/platform/zhgh/brand/summary/index.html")
public void summaryIndex() {
}
/**
* 品牌工作室管理页面
*/
@At("/office/index")
@Ok("beetl:/platform/zhgh/brand/office/index.html")
public void officeIndex() {
}
}
@@ -0,0 +1,14 @@
package com.budwk.app.zhgh.brand.convert;
import cn.hutool.core.bean.BeanUtil;
import com.budwk.app.zhgh.brand.models.BrandApplicationFlow;
import com.budwk.app.zhgh.brand.domain.vo.BrandApplicationFlowVO;
import java.util.List;
public class BrandApplicationFlowConvert {
public static List<BrandApplicationFlowVO> convertList(List<BrandApplicationFlow> list) {
return BeanUtil.copyToList(list, BrandApplicationFlowVO.class);
}
}
@@ -0,0 +1,41 @@
package com.budwk.app.zhgh.brand.convert;
import cn.hutool.core.bean.BeanUtil;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioApplicationCreateDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioApplicationUpdateDTO;
import com.budwk.app.zhgh.brand.models.BrandStudioApplication;
import com.budwk.app.zhgh.brand.domain.vo.BrandStudioApplicationVO;
import com.budwk.app.zhgh.brand.domain.vo.BrandStudioApplicationHonorVO;
import java.util.List;
public class BrandStudioApplicationConvert {
public static BrandStudioApplication convert(BrandStudioApplicationCreateDTO dto) {
BrandStudioApplication brandStudioApplication = new BrandStudioApplication();
BeanUtil.copyProperties(dto, brandStudioApplication);
return brandStudioApplication;
}
public static BrandStudioApplication convert(BrandStudioApplicationUpdateDTO dto) {
BrandStudioApplication brandStudioApplication = new BrandStudioApplication();
BeanUtil.copyProperties(dto, brandStudioApplication);
return brandStudioApplication;
}
public static BrandStudioApplicationVO convert(BrandStudioApplicationHonorVO application) {
BrandStudioApplicationVO vo = new BrandStudioApplicationVO();
BeanUtil.copyProperties(application, vo);
return vo;
}
public static BrandStudioApplicationVO convert(BrandStudioApplication application) {
BrandStudioApplicationVO vo = new BrandStudioApplicationVO();
BeanUtil.copyProperties(application, vo);
return vo;
}
public static List<BrandStudioApplicationVO> convertList(List<BrandStudioApplication> list) {
return BeanUtil.copyToList(list, BrandStudioApplicationVO.class);
}
}
@@ -0,0 +1,24 @@
package com.budwk.app.zhgh.brand.convert;
import cn.hutool.core.bean.BeanUtil;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioHonorCreateDTO;
import com.budwk.app.zhgh.brand.models.BrandStudioHonor;
import com.budwk.app.zhgh.brand.domain.vo.BrandStudioApplicationHonorVO;
import com.budwk.app.zhgh.brand.domain.vo.BrandStudioHonorVO;
import java.util.List;
public class BrandStudioHonorConvert {
public static List<BrandStudioApplicationHonorVO> convertList(List<BrandStudioHonor> list) {
return BeanUtil.copyToList(list, BrandStudioApplicationHonorVO.class);
}
public static List<BrandStudioHonorVO> convertListVO(List<BrandStudioHonor> honorList) {
return BeanUtil.copyToList(honorList, BrandStudioHonorVO.class);
}
public static List<BrandStudioHonor> convertListDTO(List<BrandStudioHonorCreateDTO> honorList) {
return BeanUtil.copyToList(honorList, BrandStudioHonor.class);
}
}
@@ -0,0 +1,19 @@
package com.budwk.app.zhgh.brand.convert;
import cn.hutool.core.bean.BeanUtil;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioMemberCreateDTO;
import com.budwk.app.zhgh.brand.models.BrandStudioMember;
import com.budwk.app.zhgh.brand.domain.vo.BrandStudioMemberVO;
import java.util.List;
public class BrandStudioMemberConvert {
public static List<BrandStudioMember> convertList(List<BrandStudioMemberCreateDTO> list) {
return BeanUtil.copyToList(list, BrandStudioMember.class);
}
public static List<BrandStudioMemberVO> convertListVO(List<BrandStudioMember> list) {
return BeanUtil.copyToList(list, BrandStudioMemberVO.class);
}
}
@@ -0,0 +1,57 @@
package com.budwk.app.zhgh.brand.domain.dto;
import lombok.Data;
import java.util.List;
/**
* 品牌工作室申请审核
*/
@Data
public class BrandApplicationAuditDTO {
/**
* 申请 Id
*/
private String applicationId;
/**
* 状态
*/
private Integer status;
/**
* 待处理的 状态流 id
*/
private String flowId;
/**
* 备注
*/
private String remark;
/**
* 流程实例ID
*/
private String processInstanceId;
/**
* 流程业务ID
*/
private String processInstanceBusinessId;
/**
* 流程任务ID
*/
private String processInstanceTaskId;
/**
* 任务审批类型
*/
private String bpmTaskApprovalType;
/**
* 下一个任务接收人
*/
private List<String> assignments;
}
@@ -0,0 +1,24 @@
package com.budwk.app.zhgh.brand.domain.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 申请撤回
*/
@Data
public class BrandApplicationFlowRecallDTO {
/**
* 当前流程ID
*/
@NotBlank(message = "当前流程ID不能为空")
private String curFlowId;
/**
* 下个流程ID
*/
@NotBlank(message = "下个流程ID不能为空")
private String nextFlowId;
}
@@ -0,0 +1,26 @@
package com.budwk.app.zhgh.brand.domain.dto;
import lombok.Data;
/**
* 申请撤回
*/
@Data
public class BrandApplicationRecallDTO {
/**
* 操作流程 ID
*/
private String flowId;
/**
* 当前申请 ID
*/
private String applicationId;
/**
* 流程任务ID
*/
private String processInstanceTaskId;
}
@@ -0,0 +1,140 @@
package com.budwk.app.zhgh.brand.domain.dto;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;
@Data
public class BrandStudioApplicationCreateDTO {
/**
* 工作室名称
*/
@NotBlank(message = "工作室名称不能为空")
private String name;
/**
* 工会ID
*/
@NotBlank(message = "所属工会不能为空")
private String unionId;
/**
* 工会名称
*/
@NotBlank(message = "所属工会不能为空")
private String unionName;
/**
* 创建年份(格式:2025)
*/
@NotBlank(message = "创建年份不能为空")
@Pattern(regexp = "^\\d{4}$", message = "创建年份格式必须为4位年份")
private String createYear;
/**
* 工作室领衔人
*/
@NotBlank(message = "领衔人不能为空")
@Size(max = 20, message = "领衔人最多20个字符")
private String leadPeople;
/**
* 领衔人年龄
*/
@NotNull(message = "领衔人年龄不能为空")
@Min(value = 0, message = "年龄不能小于0")
@Max(value = 150, message = "年龄不能超过150")
private Integer leadAge;
/**
* 领衔人身份(多个用逗号分隔)
*/
@NotBlank(message = "领衔人身份不能为空")
private String leadIdentity;
/**
* 工作室类型
*/
@NotBlank(message = "工作室类型不能为空")
private String type;
/**
* 所属赛道
*/
@NotBlank(message = "赛道不能为空")
private String track;
/**
* 工作室具体位置
*/
@NotBlank(message = "地址不能为空")
private String address;
/**
* 成员人数
*/
@NotNull(message = "成员人数不能为空")
@Min(value = 1, message = "成员人数至少1人")
private Integer memberNum;
/**
* 工作室介绍
*/
@NotBlank(message = "工作室介绍不能为空")
private String introduction;
/**
* 申请状态
*/
@NotNull(message = "申请状态不能为空")
private Integer status;
/**
* 是否归档(0-否,1-是)
*/
private Integer archive;
/**
* 创建人姓名
*/
private String createName;
/**
* 所属机构ID
*/
@NotBlank(message = "所属机构不能为空")
private String deptid;
/**
* 机构名称
*/
@NotBlank(message = "所属机构不能为空")
private String deptName;
/**
* 所属领域
*/
@NotBlank(message = "所属领域不能为空")
private String domain;
/**
* 领衔人荣誉称号
*/
@NotBlank(message = "领衔人荣誉不能为空")
private String leadHonor;
/**
* 备注
*/
private String remark;
/**
* 工作室成员列表
*/
@NotEmpty(message = "至少需要1位成员")
@Valid
private List<BrandStudioMemberCreateDTO> brandStudioMembers;
}
@@ -0,0 +1,149 @@
package com.budwk.app.zhgh.brand.domain.dto;
import com.budwk.app.base.param.PageForm;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* 品牌工作室申请表
*/
@EqualsAndHashCode(callSuper = false)
@Data
public class BrandStudioApplicationPageDTO extends PageForm {
private Integer page = 1;
private Integer limit = 10;
/**
* 工作室ID,主键
*/
private String id;
/**
* 工作室名称
*/
private String name;
/**
* 申请人ID,唯一
*/
private String userId;
/**
* 申请人所属工会ID
*/
private String unionId;
private List<String> unionIds;
/**
* 工会名称
*/
private String unionName;
/**
* 创建年份(格式如:2025)
*/
private String createYear;
/**
* 工作室领衔人
*/
private String leadPeople;
/**
* 领衔人年龄
*/
private Integer leadAge;
/**
* 领衔人身份,多个身份用逗号分隔
*/
private String leadIdentity;
/**
* 工作室类型
*/
private String type;
/**
* 所属赛道
*/
private String track;
/**
* 工作室具体位置
*/
private String address;
/**
* 成员人数
*/
private Integer memberNum;
/**
* 工作室介绍
*/
private String introduction;
/**
* 申请进度
*/
private Integer status;
/**
* 归档
*/
private Integer archive;
/**
* 创建人姓名
*/
private String createName;
/**
* 所属机构
*/
private String deptid;
/**
* 机构名称
*/
private String deptName;
/**
* 所属领域
*/
private String domain;
/**
* 领衔人荣誉称号
*/
private String leadHonor;
/**
* 备注
*/
private String remark;
/**
* 工作流状态
*/
private Integer processStatus;
@Override
public Integer getPageNumber() {
Integer pageNumber = super.getPageNumber();
return pageNumber == null ? page : pageNumber;
}
@Override
public Integer getPageSize() {
Integer pageSize = super.getPageSize();
return pageSize == null ? limit : pageSize;
}
}
@@ -0,0 +1,112 @@
package com.budwk.app.zhgh.brand.domain.dto;
import lombok.Data;
import java.util.List;
@Data
public class BrandStudioApplicationUpdateDTO {
/**
* 工作室ID,主键
*/
private String id;
/**
* 工作室名称
*/
private String name;
/**
* 创建年份(格式如:2025)
*/
private String createYear;
/**
* 工作室领衔人
*/
private String leadPeople;
/**
* 领衔人年龄
*/
private Integer leadAge;
/**
* 领衔人身份,多个身份用逗号分隔
*/
private String leadIdentity;
/**
* 工作室类型
*/
private String type;
/**
* 所属赛道
*/
private String track;
/**
* 工作室具体位置
*/
private String address;
/**
* 成员人数
*/
private Integer memberNum;
/**
* 工作室介绍
*/
private String introduction;
/**
* 申请进度
*/
private Integer status;
/**
* 归档
*/
private Integer archive;
/**
* 创建人姓名
*/
private String createName;
/**
* 所属机构
*/
private String deptid;
/**
* 机构名称
*/
private String deptName;
/**
* 所属领域
*/
private String domain;
/**
* 领衔人荣誉称号
*/
private String leadHonor;
/**
* 备注
*/
private String remark;
/**
* 添加的成员
* */
private List<BrandStudioMemberCreateDTO> brandStudioMembers;
private List<BrandStudioHonorCreateDTO> brandStudioHonors;
}
@@ -0,0 +1,34 @@
package com.budwk.app.zhgh.brand.domain.dto;
import lombok.Data;
@Data
public class BrandStudioHonorCreateDTO {
/**
* 工作室申请ID
*/
private String applicationId;
/**
* 荣誉称号
*/
private String honorName;
/**
* 级别
*/
private String honorGrade;
/**
* 获取年份
*/
private String requireYear;
/**
* 荣誉类别:外部荣誉,行内荣誉
* 通常可定义为枚举或常量:
* 例如:1 - 外部荣誉,2 - 行内荣誉
*/
private Short honorCategory;
}
@@ -0,0 +1,79 @@
package com.budwk.app.zhgh.brand.domain.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
/**
* 工作室成员信息表实体类
*/
@Data
public class BrandStudioMemberCreateDTO {
/**
* 工作室ID(外键,关联到 BrandStudioApplication
*/
private String applicationId;
/**
* 用户 ID
*/
private String userId;
/**
* 用户 工号
*/
private String emplid;
/**
* 成员姓名
*/
@NotBlank(message = "成员姓名不能为空")
private String name;
/**
* 成员角色(如:核心成员、技术骨干、助理等)
*/
private String role;
/**
* 成员出生年月(建议格式:yyyy-MM 或 yyyy,实际为字符串存储)
*/
@NotBlank(message = "成员出生年月不能为空")
private String birthdate;
/**
* 学历(如:本科、硕士、博士等)
*/
@NotBlank(message = "学历不能为空")
private String grade;
/**
* 职称(如:高级工程师、教授、技师等)
*/
@NotBlank(message = "职称不能为空")
private String professional;
/**
* 所在部门
*/
private String deptName;
/**
* 加入日期
*/
private LocalDateTime joinDate;
/**
* 离开日期(可为空,表示尚未离职)
*/
private LocalDateTime leaveDate;
/**
* 成员状态:
* 0 - 在职,
* 1 - 离职
*/
private Integer status;
}
@@ -0,0 +1,79 @@
package com.budwk.app.zhgh.brand.domain.dto;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 工作室成员信息表实体类
*/
@Data
public class BrandStudioMemberUpdateDTO {
/**
* ID
*/
private String id;
/**
* 工作室ID(外键,关联到 BrandStudioApplication
*/
private String applicationId;
/**
* 用户 ID
*/
private String userId;
/**
* 用户 工号
*/
private String emplid;
/**
* 成员姓名
*/
private String name;
/**
* 成员角色(如:核心成员、技术骨干、助理等)
*/
private String role;
/**
* 成员出生年月(建议格式:yyyy-MM 或 yyyy,实际为字符串存储)
*/
private String birthdate;
/**
* 学历(如:本科、硕士、博士等)
*/
private String grade;
/**
* 职称(如:高级工程师、教授、技师等)
*/
private String professional;
/**
* 所在部门
*/
private String deptName;
/**
* 加入日期
*/
private LocalDateTime joinDate;
/**
* 离开日期(可为空,表示尚未离职)
*/
private LocalDateTime leaveDate;
/**
* 成员状态:
* 0 - 在职,
* 1 - 离职
*/
private Integer status;
}
@@ -0,0 +1,59 @@
package com.budwk.app.zhgh.brand.domain.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 荣誉申请状态
*/
@Getter
@AllArgsConstructor
public enum ApplicationFlowStatusEnum
{
/**
* 等待审核
*/
WAIT_AUDIT(0),
/**
* 待审核/审核中
*/
TO_BE_AUDIT(1),
/**
* 驳回
*/
REJECT(2),
/**
* 撤销
*/
REPEAL(3),
/**
* 撤回 -> 取消
*/
RECALL(4),
/**
* 通过
*/
PASSED(5),
;
private final int value;
public Integer getValue() {
return this.value;
}
/**
* 根据状态码获取对应的枚举项
*
* @param value 状态码
* @return 对应的枚举,若不存在则返回 null
*/
public static ApplicationFlowStatusEnum getStatusEnumByValue(int value) {
for (ApplicationFlowStatusEnum status : values()) {
if (status.value == value) {
return status;
}
}
return null; // 未找到时返回 null
}
}
@@ -0,0 +1,42 @@
package com.budwk.app.zhgh.brand.domain.enums;
import lombok.Getter;
/**
* 品牌工作室申请进度状态枚举
*/
@Getter
public enum BrandApplicationStatusEnums {
DRAFT(0, "草稿"),
IN_REVIEW(1, "审核中/提交申请"),
REJECT(2, "驳回"),
REPEAL(3, "撤销"),
RECALL(4, "撤回"),
PASSED(5, "通过"),
CREATING(6, "正在创建"),
FAIL(7, "创建失败");
private final int code;
private final String description;
BrandApplicationStatusEnums(int code, String description) {
this.code = code;
this.description = description;
}
/**
* 根据状态码获取对应的枚举项
*
* @param code 状态码
* @return 对应的枚举,若不存在则返回 null
*/
public static BrandApplicationStatusEnums getStatusByCode(int code) {
for (BrandApplicationStatusEnums status : values()) {
if (status.code == code) {
return status;
}
}
return null; // 未找到时返回 null
}
}
@@ -0,0 +1,43 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class ApplicationProgressVO {
/** 申请单 ID */
private Long applicationId;
/** 申请工作室名称 */
private String applicationName;
/** 申请工会 ID */
private String unionId;
/** 申请人直属工会名称 */
private String unionName;
/** 用户ID */
private Long userId;
/** 申请单状态 */
private Integer status;
/** 牵头人 */
private String leadPeople;
/** 申请时间 */
private LocalDateTime createTime;
/** 总节点数 */
private Integer totalNodes;
/** 已通过的节点数 */
private Integer passedNodes;
/** 进度百分比(0-100),保留两位小数 */
private BigDecimal progressPct;
}
@@ -0,0 +1,29 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class ApplicationWarnVO {
private String applicationId;
private String name;
private String leadPeople;
private Integer status;
private String unionId;
private String unionName;
private LocalDateTime receiveTime;
private BigDecimal pendingDays;
private Integer warnStatus;
private BigDecimal avgAuditDays;
}
@@ -0,0 +1,66 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* 申请流程 VO
*/
@EqualsAndHashCode(callSuper = false)
@Data
public class BrandApplicationFlowVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private String id;
/**
* 工会ID
*/
private String unionId;
/**
* 工会
*/
private String unionName;
/**
* 审核人ID
*/
private String auditorId;
/**
* 审核人
*/
private String auditor;
/**
* 申请ID
*/
private String applicationId;
/**
* 状态(0:待审核;1:驳回;2:撤回;3:撤销;4:通过)
*/
private Integer status;
/**
* 审批内容(意见描述)
*/
private String content;
/**
* 备注
*/
private String remark;
/**
* 序号
*/
private Integer sortNo;
}
@@ -0,0 +1,28 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
@Data
public class BrandFlowUserVO {
/**
* 申请人ID,唯一
*/
private String userId;
/**
* 流程 ID
*/
private String flowId;
/**
* 流程 状态
*/
private Integer flowStatus;
/**
* 流程 序号
*/
private Integer sortNo;
}
@@ -0,0 +1,58 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
/**
* 品牌工作室荣誉导出写死
*/
@Data
public class BrandStudioApplicationHonorExport {
private Integer index;
/**
* 工作室名称
*/
private String name;
/**
* 创建年份(格式如:2025)
*/
private String createYear;
/**
* 工作室领衔人
*/
private String leadPeople;
/**
* 机构名称
*/
private String deptName;
/**
* 荣誉称号
*/
private String honorName;
/**
* 所属赛道
*/
private String track;
/**
* 领衔人荣誉称号
*/
private String leadHonor;
/**
* 成员人数
*/
private Integer memberNum;
/**
* 工作室具体位置
*/
private String address;
}
@@ -0,0 +1,40 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 品牌工作室荣誉
*/
@EqualsAndHashCode(callSuper = false)
@Data
public class BrandStudioApplicationHonorVO extends BrandStudioApplicationVO {
/**
* 工作室荣誉ID,主键
*/
private String honorId;
/**
* 荣誉称号
*/
private String honorName;
/**
* 级别
*/
private String honorGrade;
/**
* 获取年份
*/
private String requireYear;
/**
* 荣誉类别:外部荣誉,行内荣誉
* 通常可定义为枚举或常量:
* 例如:1 - 外部荣誉,2 - 行内荣誉
*/
private Short honorCategory;
}
@@ -0,0 +1,152 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.List;
/**
* 品牌工作室
*/
@EqualsAndHashCode(callSuper = false)
@Data
public class BrandStudioApplicationVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private String id;
/**
* 工作室名称
*/
private String name;
/**
* 工会ID
*/
private String unionId;
/**
* 工会名称
*/
private String unionName;
/**
* 创建年份(格式如:2025)
*/
private String createYear;
/**
* 工作室领衔人
*/
private String leadPeople;
/**
* 领衔人年龄
*/
private Integer leadAge;
/**
* 领衔人身份,多个身份用逗号分隔
*/
private String leadIdentity;
/**
* 工作室类型
*/
private String type;
/**
* 所属赛道
*/
private String track;
/**
* 工作室具体位置
*/
private String address;
/**
* 成员人数
*/
private Integer memberNum;
/**
* 工作室介绍
*/
private String introduction;
/**
* 申请进度
*/
private Integer status;
/**
* 归档
*/
private Integer archive;
/**
* 流程 ID
*/
private String flowId;
/**
* 流程 工会名称
*/
private String flowUnionName;
/**
* 流程 状态
*/
private Integer flowStatus;
/**
* 流程 序号
*/
private Integer flowSortNo;
/**
* 创建人姓名
*/
private String createName;
/**
* 所属机构
*/
private String deptid;
/**
* 机构名称
*/
private String deptName;
/**
* 所属领域
*/
private String domain;
/**
* 领衔人荣誉称号
*/
private String leadHonor;
/**
* 备注
*/
private String remark;
/**
* 添加的成员
* */
private List<BrandStudioMemberVO> brandStudioMembers;
/**
* 荣誉相关信息
* */
private List<BrandStudioHonorVO> brandStudioHonors;
}
@@ -0,0 +1,168 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 审核返回数据
*/
@EqualsAndHashCode(callSuper = false)
@Data
public class BrandStudioApplyFlowVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private String id;
private String applicationId;
/**
* 工作室名称
*/
private String name;
/**
* 工会ID
*/
private String unionId;
/**
* 工会名称
*/
private String unionName;
/**
* 创建年份(格式如:2025)
*/
private String createYear;
/**
* 工作室领衔人
*/
private String leadPeople;
/**
* 领衔人年龄
*/
private Integer leadAge;
/**
* 领衔人身份,多个身份用逗号分隔
*/
private String leadIdentity;
/**
* 工作室类型
*/
private String type;
/**
* 所属赛道
*/
private String track;
/**
* 工作室具体位置
*/
private String address;
/**
* 成员人数
*/
private Integer memberNum;
/**
* 工作室介绍
*/
private String introduction;
/**
* 申请进度
*/
private Integer status;
/**
* 归档
*/
private Integer archive;
/**
* 流程 ID
*/
private String flowId;
/**
* 流程 工会名称
*/
private String flowUnionName;
/**
* 流程 状态
*/
private Integer flowStatus;
/**
* 流程 序号
*/
private Integer flowSortNo;
/**
* 创建人姓名
*/
private String createName;
/**
* 所属机构
*/
private String deptid;
/**
* 机构名称
*/
private String deptName;
/**
* 所属领域
*/
private String domain;
/**
* 领衔人荣誉称号
*/
private String leadHonor;
/**
* 备注
*/
private String remark;
private Integer processUserTaskStatus;
private Date processTaskStartTime;
private Date processTaskEndTime;
private String processTaskReason;
private String processTaskNode;
private String processInstanceId;
private String processInstanceTaskId;
}
@@ -0,0 +1,39 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
@Data
public class BrandStudioHonorVO {
/**
* 工作室荣誉ID,主键
*/
private String id;
/**
* 工作室申请ID
*/
private String applicationId;
/**
* 荣誉称号
*/
private String honorName;
/**
* 级别
*/
private String honorGrade;
/**
* 获取年份
*/
private String requireYear;
/**
* 荣誉类别:外部荣誉,行内荣誉
* 通常可定义为枚举或常量:
* 例如:1 - 外部荣誉,2 - 行内荣誉
*/
private Short honorCategory;
}
@@ -0,0 +1,84 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 工作室成员信息表实体类
*/
@EqualsAndHashCode(callSuper = false)
@Data
public class BrandStudioMemberVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private String id;
/**
* 工作室ID(外键,关联到 BrandStudioApplication
*/
private String applicationId;
/**
* 用户 ID
*/
private String userId;
/**
* 用户 工号
*/
private String emplid;
/**
* 成员姓名
*/
private String name;
/**
* 成员角色(如:核心成员、技术骨干、助理等)
*/
private String role;
/**
* 成员出生年月(建议格式:yyyy-MM 或 yyyy,实际为字符串存储)
*/
private String birthdate;
/**
* 学历(如:本科、硕士、博士等)
*/
private String grade;
/**
* 职称(如:高级工程师、教授、技师等)
*/
private String professional;
/**
* 所在部门
*/
private String deptName;
/**
* 加入日期
*/
private LocalDateTime joinDate;
/**
* 离开日期(可为空,表示尚未离职)
*/
private LocalDateTime leaveDate;
/**
* 成员状态:
* 0 - 在职,
* 1 - 离职
*/
private Integer status;
}
@@ -0,0 +1,13 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
@Data
public class DeptInfoVO {
private String deptid;
private String deptName;
private String topParentNextId;
private String topParentNextName;
}
@@ -0,0 +1,21 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
import java.util.List;
@Data
public class LeaderHonorGroupVO {
private String emplid;
private String userName;
private Integer age;
private List<String> honorNames;
public LeaderHonorGroupVO(String emplid, String userName, Integer age, List<String> honorNames) {
this.emplid = emplid;
this.userName = userName;
this.age = age;
this.honorNames = honorNames;
}
}
@@ -0,0 +1,130 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 领衔人荣誉
*/
@Data
public class LeaderHonorVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
private String id;
/**
* 姓名
*/
//@Excel(name = "姓名")
private String userName;
/**
* 年龄
*/
//@Excel(name = "年龄")
private Integer age;
/**
* 人员编号(工号)
*/
//@Excel(name = "人员编号(工号)")
private String emplid;
/**
* 机构
*/
//@Excel(name = "机构")
private String deptid;
/**
* 工会ID
*/
//@Excel(name = "工会ID")
private String unionId;
/**
* 机构名称
*/
//@Excel(name = "机构名称")
private String deptName;
/**
* 年度
*/
//@Excel(name = "年度")
private String year;
/**
* 内外部荣誉
*/
//@Excel(name = "内外部荣誉")
private String categoryName1;
/**
* 内外部荣誉编号
*/
//@Excel(name = "内外部荣誉编号")
private String categoryCode1;
/**
* 荣誉级别名称
*/
//@Excel(name = "荣誉级别名称")
private String categoryName2;
/**
* 荣誉级别编号
*/
//@Excel(name = "荣誉级别编号")
private String categoryCode2;
/**
* 荣誉类别名称
*/
//@Excel(name = "荣誉类别名称")
private String categoryName3;
/**
* 荣誉类别编号
*/
//@Excel(name = "荣誉类别编号")
private String categoryCode3;
/**
* 荣誉名称
*/
//@Excel(name = "荣誉名称")
private String honorName;
/**
* 荣誉编号
*/
//@Excel(name = "荣誉编号")
private String honorCode;
/**
* 荣誉分类id
*/
private String categoryCode;
/**
* 当前荣誉分类名称
*/
private String categoryName;
/**
* 评选对象(机构/个人)
*/
//@Excel(name = "评选对象(机构/个人)")
private String selectionType;
/**
* 状态
*/
private Integer status;
}
@@ -0,0 +1,96 @@
package com.budwk.app.zhgh.brand.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.List;
/**
* 机构列表
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class SysOrgVO {
/**
* 机构编号
*/
private String orgId;
/**
* 上级编号
*/
private String parentId;
/**
* 子节点
*/
private List<SysOrgVO> children;
/**
* 机构名称
*/
private String orgName;
/**
* 机构代码
*/
private String orgCode;
/**
* 排序
*/
private Integer sort;
/**
* 负责人ID
*/
private String leaderId;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 上级名称
*/
private String parentName;
/** 分行ID */
//@Excel(name = "分行ID")
private String branchId;
/** 机构类型 */
//@Excel(name = "机构类型")
private String orgType;
/** 描述 */
//@Excel(name = "描述")
private String description;
/** 机构状态 */
//@Excel(name = "机构状态")
private String status;
/** 党组织md5 */
//@Excel(name = "党组织md5")
private String orgMd5;
/** 自定义属性 */
//@Excel(name = "自定义属性")
private String countryCode;
/** 工会ID */
private String unionId;
/** 工会ID */
private String childUnionId;
/** 机构中文全称 */
private String gcDeptLdescr;
}
@@ -0,0 +1,43 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
@Data
public class SysUnionDeptVO {
/**
* userId
*/
private String userId;
/**
* unionId
*/
private String unionId;
/**
* 工会名称
*/
private String unionName;
/**
* deptId
*/
private String deptId;
/**
* 机构名称
*/
private String deptName;
/**
* 最上级工会 Id
*/
private String topUnionId;
/**
* 最上级工会名称
*/
private String topUnionName;
}
@@ -0,0 +1,56 @@
package com.budwk.app.zhgh.brand.domain.vo;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 工会-用户 工作室申请统计视图对象
* 按 union_id + user_id 聚合,统计每个用户在其所属工会下的所有申请单的状态分布。
* 所有比例字段单位为 %,已乘以 100 并保留 2 位小数。
* @author chen
* @since 2026-01-08
*/
@Data
public class UnionApplyStatsVO implements Serializable {
private static final long serialVersionUID = 1L;
/** 工会ID */
private String unionId;
/** 工会名称 */
private String unionName;
/** 用户姓名(来自 sys_user */
private String username;
/** 申请单总量 */
private Integer applyCnt;
/** 已通过数量(status = 5 */
private Integer passedCnt;
/** 通过率(% */
private BigDecimal passRate;
/** 审核中数量(status = 1 */
private Integer reviewCnt;
/** 审核中占比(% */
private BigDecimal reviewRate;
/** 已驳回数量(status = 2 */
private Integer rejectCnt;
/** 驳回率(% */
private BigDecimal rejectRate;
/** 已撤销数量(status = 3 */
private Integer repealCnt;
/** 撤销率(% */
private BigDecimal repealRate;
}
@@ -0,0 +1,99 @@
package com.budwk.app.zhgh.brand.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 品牌工作室申请流程表
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("brand_application_flow")
@Comment("品牌工作室申请流程表")
public class BrandApplicationFlow extends BaseModel implements Serializable {
/**
* 审批记录ID,主键
*/
@Name
@Column
@Comment("审批记录ID,主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
/**
* 工会ID
*/
@Column
@Comment("工会ID")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String unionId;
/**
* 工会
*/
@Column
@Comment("工会")
@ColDefine(type = ColType.VARCHAR, width = 256)
private String unionName;
/**
* 审核人ID
*/
@Column
@Comment("审核人ID")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String auditorId;
/**
* 审核人
*/
@Column
@Comment("审核人")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String auditor;
/**
* 申请ID
*/
@Column
@Comment("申请ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String applicationId;
/**
* 状态(0:待审核;1:驳回;2:撤回;3:撤销;4:通过)
*/
@Column
@Comment("状态")
private Integer status;
/**
* 审批内容(意见描述)
*/
@Column
@Comment("审批内容(意见描述)")
@ColDefine(type = ColType.VARCHAR, customType = "text")
private String content;
/**
* 备注
*/
@Column
@Comment("备注")
@ColDefine(type = ColType.VARCHAR, customType = "text")
private String remark;
/**
* 序号
*/
@Column
@Comment("序号")
private Integer sortNo;
}
@@ -0,0 +1,193 @@
package com.budwk.app.zhgh.brand.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 品牌工作室申请表实体类
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("brand_studio_application")
@Comment("品牌工作室申请表")
public class BrandStudioApplication extends BaseModel implements Serializable {
/**
* 工作室ID,主键
*/
@Name
@Column
@Comment("工作室ID,主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
/**
* 工作室名称
*/
@Column
@Comment("工作室名称")
@ColDefine(type = ColType.VARCHAR, width = 128)
private String name;
/**
* 申请人ID,唯一
*/
@Column
@Comment("申请人ID,唯一")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String userId;
/**
* 申请人所属工会ID
*/
@Column
@Comment("申请人所属工会ID")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String unionId;
/**
* 工会名称
*/
@Column
@Comment("工会名称")
@ColDefine(type = ColType.VARCHAR, width = 256)
private String unionName;
/**
* 创建年份(格式如:2025)
*/
@Column
@Comment("创建年份")
@ColDefine(type = ColType.VARCHAR, width = 10)
private String createYear;
/**
* 工作室领衔人
*/
@Column
@Comment("工作室领衔人")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String leadPeople;
/**
* 领衔人年龄
*/
@Column
@Comment("领衔人年龄")
private Integer leadAge;
/**
* 领衔人身份,多个身份用逗号分隔
*/
@Column
@Comment("领衔人身份,多个身份用逗号分隔")
@ColDefine(type = ColType.VARCHAR, customType = "text")
private String leadIdentity;
/**
* 工作室类型
*/
@Column
@Comment("工作室类型")
@ColDefine(type = ColType.VARCHAR, width = 128)
private String type;
/**
* 所属赛道
*/
@Column
@Comment("所属赛道")
@ColDefine(type = ColType.VARCHAR, width = 128)
private String track;
/**
* 工作室具体位置
*/
@Column
@Comment("工作室具体位置")
@ColDefine(type = ColType.VARCHAR, width = 256)
private String address;
/**
* 成员人数
*/
@Column
@Comment("成员人数")
private Integer memberNum;
/**
* 工作室介绍
*/
@Column
@Comment("工作室介绍")
@ColDefine(type = ColType.VARCHAR, customType = "text")
private String introduction;
/**
* 申请进度
*/
@Column
@Comment("申请进度")
private Integer status;
/**
* 归档
*/
@Column
@Comment("归档")
private Integer archive;
/**
* 创建人姓名
*/
@Column
@Comment("创建人姓名")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String createName;
/**
* 所属机构
*/
@Column
@Comment("所属机构")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String deptid;
/**
* 机构名称
*/
@Column
@Comment("机构名称")
@ColDefine(type = ColType.VARCHAR, width = 256)
private String deptName;
/**
* 所属领域
*/
@Column
@Comment("所属领域")
@ColDefine(type = ColType.VARCHAR, width = 128)
private String domain;
/**
* 领衔人荣誉称号
*/
@Column
@Comment("领衔人荣誉称号")
@ColDefine(type = ColType.VARCHAR, width = 500)
private String leadHonor;
/**
* 备注
*/
@Column
@Comment("备注")
@ColDefine(type = ColType.VARCHAR, customType = "text")
private String remark;
}
@@ -0,0 +1,70 @@
package com.budwk.app.zhgh.brand.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
/**
* 工作室荣誉信息实体类
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("brand_studio_honor")
@Comment("品牌工作室荣誉信息表")
public class BrandStudioHonor extends BaseModel implements Serializable {
/**
* 工作室荣誉ID,主键
*/
@Name
@Column
@Comment("工作室荣誉ID,主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
/**
* 工作室申请ID
*/
@Column
@Comment("工作室申请ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String applicationId;
/**
* 荣誉称号
*/
@Column
@Comment("荣誉称号")
@ColDefine(type = ColType.VARCHAR, width = 128)
private String honorName;
/**
* 级别
*/
@Column
@Comment("级别")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String honorGrade;
/**
* 获取年份
*/
@Column
@Comment("获取年份")
@ColDefine(type = ColType.VARCHAR, width = 10)
private String requireYear;
/**
* 荣誉类别:外部荣誉,行内荣誉
* 通常可定义为枚举或常量:
* 例如:1 - 外部荣誉,2 - 行内荣誉
*/
@Column
@Comment("荣誉类别")
private Short honorCategory;
}
@@ -0,0 +1,127 @@
package com.budwk.app.zhgh.brand.models;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.io.Serializable;
import java.util.Date;
/**
* 工作室成员信息表实体类
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Table("brand_studio_member")
@Comment("品牌工作室成员信息表")
public class BrandStudioMember extends BaseModel implements Serializable {
/**
* 成员ID,主键
*/
@Name
@Column
@Comment("成员ID,主键")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
/**
* 工作室ID(外键,关联到 BrandStudioApplication
*/
@Column
@Comment("工作室ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String applicationId;
/**
* 用户 ID
*/
@Column
@Comment("用户ID")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String userId;
/**
* 用户 工号
*/
@Column
@Comment("用户工号")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String emplid;
/**
* 成员姓名
*/
@Column
@Comment("成员姓名")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String name;
/**
* 成员角色(如:核心成员、技术骨干、助理等)
*/
@Column
@Comment("成员角色")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String role;
/**
* 成员出生年月(建议格式:yyyy-MM 或 yyyy,实际为字符串存储)
*/
@Column
@Comment("成员出生年月")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String birthdate;
/**
* 学历(如:本科、硕士、博士等)
*/
@Column
@Comment("学历")
@ColDefine(type = ColType.VARCHAR, width = 64)
private String grade;
/**
* 职称(如:高级工程师、教授、技师等)
*/
@Column
@Comment("职称")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String professional;
/**
* 所在部门
*/
@Column
@Comment("所在部门")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String deptName;
/**
* 加入日期
*/
@Column
@Comment("加入日期")
@ColDefine(type = ColType.DATETIME)
private Date joinDate;
/**
* 离开日期(可为空,表示尚未离职)
*/
@Column
@Comment("离开日期")
@ColDefine(type = ColType.DATETIME)
private Date leaveDate;
/**
* 成员状态:
* 0 - 在职,
* 1 - 离职
*/
@Column
@Comment("成员状态")
private Integer status;
}
@@ -0,0 +1,24 @@
package com.budwk.app.zhgh.brand.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.brand.domain.dto.BrandApplicationAuditDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandApplicationRecallDTO;
import com.budwk.app.zhgh.brand.domain.enums.BrandApplicationStatusEnums;
import com.budwk.app.zhgh.brand.domain.vo.BrandApplicationFlowVO;
import com.budwk.app.zhgh.brand.models.BrandApplicationFlow;
import com.budwk.app.sys.models.Sys_user;
import java.util.List;
public interface BrandApplicationFlowService extends BaseService<BrandApplicationFlow> {
void submitAudit(String applicationId, BrandApplicationStatusEnums statusEnums, Sys_user user);
void audit(BrandApplicationAuditDTO dto);
void recall(BrandApplicationRecallDTO dto);
List<BrandApplicationFlowVO> getFlowList(String applicationId);
void deleteByApplicationIds(List<String> ids);
}
@@ -0,0 +1,18 @@
package com.budwk.app.zhgh.brand.service;
import com.budwk.app.zhgh.brand.domain.vo.ApplicationProgressVO;
import com.budwk.app.zhgh.brand.domain.vo.ApplicationWarnVO;
import com.budwk.app.zhgh.brand.domain.vo.UnionApplyStatsVO;
import java.util.List;
import java.util.Map;
public interface BrandApplicationStatisticsService {
List<UnionApplyStatsVO> selectUnionApplyStats();
List<ApplicationProgressVO> selectApplyProgressStats();
List<ApplicationWarnVO> selectApplyWarningStats();
Map<String, Map<String, Long>> selectApplyMapCount();
}
@@ -0,0 +1,54 @@
package com.budwk.app.zhgh.brand.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.brand.domain.dto.BrandApplicationAuditDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioApplicationCreateDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioApplicationPageDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioApplicationUpdateDTO;
import com.budwk.app.zhgh.brand.domain.vo.*;
import com.budwk.app.zhgh.brand.models.BrandStudioApplication;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
public interface BrandStudioApplicationService extends BaseService<BrandStudioApplication> {
Pagination<BrandStudioApplicationVO> page(BrandStudioApplicationPageDTO pageDTO);
void save(BrandStudioApplicationCreateDTO dto);
void submitApply(BrandApplicationAuditDTO dto);
void oaAuditUpdateStatus(String id, Integer status, String reason);
void update(BrandStudioApplicationUpdateDTO dto);
void updateMemberOrHonor(BrandStudioApplicationUpdateDTO dto);
void delete(List<String> ids);
SysUnionDeptVO queryUserUnion();
BrandStudioApplicationVO getDetailById(String id);
void archive(List<String> idList);
/// 之前的工作流逻辑,已废弃
Pagination<BrandStudioApplyFlowVO> auditPage(@Valid BrandStudioApplicationPageDTO pageDTO);
/// 新的工作流逻辑
Pagination<BrandStudioApplyFlowVO> flowAuditPage(BrandStudioApplicationPageDTO dto);
List<LeaderHonorGroupVO> queryLeaderHonor();
Pagination<BrandStudioApplicationHonorVO> summaryPage(@Valid BrandStudioApplicationPageDTO pageDTO);
List<BrandStudioApplicationHonorVO> getHonorDetailById(String id);
void export();
// 导出品牌工作室申请文档
void exportDocx(String id, HttpServletResponse response);
}
@@ -0,0 +1,10 @@
package com.budwk.app.zhgh.brand.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.brand.models.BrandStudioHonor;
public interface BrandStudioHonorService extends BaseService<BrandStudioHonor> {
}
@@ -0,0 +1,17 @@
package com.budwk.app.zhgh.brand.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioMemberCreateDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioMemberUpdateDTO;
import com.budwk.app.zhgh.brand.models.BrandStudioMember;
import java.util.List;
public interface BrandStudioMemberService extends BaseService<BrandStudioMember> {
void save(BrandStudioMemberCreateDTO dto);
void update(BrandStudioMemberUpdateDTO dto);
void delete(List<String> ids);
}
@@ -0,0 +1,308 @@
package com.budwk.app.zhgh.brand.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.bpm.enums.BpmProcessInstanceStatusEnum;
import com.budwk.app.bpm.enums.BpmTaskApprovalTypeEnum;
import com.budwk.app.bpm.models.BpmProcessInstance;
import com.budwk.app.bpm.service.BpmService;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.web.controllers.open.commons.service.CommonService;
import com.budwk.app.zhgh.brand.convert.BrandApplicationFlowConvert;
import com.budwk.app.zhgh.brand.domain.dto.BrandApplicationAuditDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandApplicationRecallDTO;
import com.budwk.app.zhgh.brand.domain.enums.ApplicationFlowStatusEnum;
import com.budwk.app.zhgh.brand.domain.enums.BrandApplicationStatusEnums;
import com.budwk.app.zhgh.brand.domain.vo.BrandApplicationFlowVO;
import com.budwk.app.zhgh.brand.domain.vo.BrandFlowUserVO;
import com.budwk.app.zhgh.brand.models.BrandApplicationFlow;
import com.budwk.app.zhgh.brand.models.BrandStudioApplication;
import com.budwk.app.zhgh.brand.service.BrandApplicationFlowService;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.ioc.loader.annotation.Inject;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class BrandApplicationFlowServiceImpl extends BaseServiceImpl<BrandApplicationFlow> implements BrandApplicationFlowService {
@Inject
private BpmService bpmService;
@Inject
private CommonService commonService;
public BrandApplicationFlowServiceImpl(Dao dao) {
super(dao);
}
/// 提交申请/待审核:插入审核流程记录表
@Override
public void submitAudit(String applicationId, BrandApplicationStatusEnums statusEnums, Sys_user user) {
if (statusEnums != BrandApplicationStatusEnums.IN_REVIEW) {
throw new RuntimeException("提交申请失败");
}
// 先获取用户所在工会的关系网 secondUnion -> firstUnion -> 0
String unionId = SecurityUtil.getUnionId();
// 两种方案:先删除之前撤回的审核记录/修改之前的撤回审核记录的状态
// 0.先删除 撤回审核记录 的 applicationId 记录,如果没有也无所谓
dao().update(BrandApplicationFlow.class, Chain.make("delFlag", true), Cnd.where("applicationId", "=", applicationId));
// 1.先插入当前用户,表示已经通过提交阶段
BrandApplicationFlow brandApplicationFlow = new BrandApplicationFlow();
brandApplicationFlow.setApplicationId(applicationId);
brandApplicationFlow.setAuditorId(user.getId());
brandApplicationFlow.setAuditor(user.getUsername());
brandApplicationFlow.setSortNo(0);
brandApplicationFlow.setStatus(ApplicationFlowStatusEnum.PASSED.getValue());
insert(brandApplicationFlow);
Sys_union union = dao().fetch(Sys_union.class, unionId);
if (union != null) {
BrandApplicationFlow flow = new BrandApplicationFlow();
flow.setApplicationId(applicationId);
flow.setUnionId(union.getId());
flow.setUnionName(union.getName());
flow.setStatus(ApplicationFlowStatusEnum.TO_BE_AUDIT.getValue());
flow.setSortNo(1);
insert(flow);
}
}
/// 审核
@Override
public void audit(BrandApplicationAuditDTO dto) {
String applicationId = StrUtil.blankToDefault(dto.getApplicationId(), dto.getProcessInstanceBusinessId());
if (StrUtil.isBlank(applicationId)) {
throw new RuntimeException("申请 Id 不能为空");
}
String taskId = StrUtil.blankToDefault(dto.getProcessInstanceTaskId(), dto.getFlowId());
if (StrUtil.isBlank(taskId)) {
throw new RuntimeException("流程任务ID不能为空");
}
BpmTaskApprovalTypeEnum approvalTypeEnum = getBpmTaskApprovalTypeEnum(dto);
HashMap<String, Object> variables = new HashMap<>(BeanUtil.beanToMap(dto));
variables.put("bpmTaskApprovalType", approvalTypeEnum.name());
variables.put("approvalOpinion", dto.getRemark());
bpmService.completeTask(taskId, approvalTypeEnum, variables, getAssignments(dto, approvalTypeEnum));
BpmProcessInstance instance = dao().fetch(BpmProcessInstance.class, Cnd.where("processInstanceBusinessId", "=", applicationId));
if (approvalTypeEnum == BpmTaskApprovalTypeEnum.REJECT) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.REJECT.getCode()),
Cnd.where("id", "=", applicationId));
} else if (approvalTypeEnum == BpmTaskApprovalTypeEnum.BACK || approvalTypeEnum == BpmTaskApprovalTypeEnum.BACK_OTHER_NODE) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.IN_REVIEW.getCode()),
Cnd.where("id", "=", applicationId));
} else if (instance != null && BpmProcessInstanceStatusEnum.COMPLETED.name().equals(instance.getProcessInstanceStatus())) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.PASSED.getCode()),
Cnd.where("id", "=", applicationId));
}
}
private List<String> getAssignments(BrandApplicationAuditDTO dto, BpmTaskApprovalTypeEnum approvalTypeEnum) {
if (dto.getAssignments() != null && !dto.getAssignments().isEmpty()) {
return dto.getAssignments();
}
if (approvalTypeEnum == BpmTaskApprovalTypeEnum.PASS) {
String approvalLoginName = commonService.findUserLoginNameByRoleCode(RoleConstant.SCHOOL_UNION_ADMIN);
if (StrUtil.isBlank(approvalLoginName)) {
throw new RuntimeException("校工会未配置审批人,请联系管理员");
}
return List.of(approvalLoginName);
}
return null;
}
private BpmTaskApprovalTypeEnum getBpmTaskApprovalTypeEnum(BrandApplicationAuditDTO dto) {
if (StrUtil.isNotBlank(dto.getBpmTaskApprovalType())) {
return BpmTaskApprovalTypeEnum.valueOf(dto.getBpmTaskApprovalType());
}
if (dto.getStatus() == null) {
throw new RuntimeException("任务审批类型错误,请传入正确的任务审批类型。");
}
ApplicationFlowStatusEnum statusEnum = ApplicationFlowStatusEnum.getStatusEnumByValue(dto.getStatus());
if (statusEnum == ApplicationFlowStatusEnum.PASSED) {
return BpmTaskApprovalTypeEnum.PASS;
} else if (statusEnum == ApplicationFlowStatusEnum.REJECT) {
return BpmTaskApprovalTypeEnum.REJECT;
} else if (statusEnum == ApplicationFlowStatusEnum.RECALL) {
return BpmTaskApprovalTypeEnum.BACK;
}
throw new RuntimeException("任务审批类型错误,请传入正确的任务审批类型。");
}
/// 审核
public void auditOld(BrandApplicationAuditDTO dto) {
BrandApplicationFlow applicationFlow = dao().fetch(BrandApplicationFlow.class, Cnd.where("applicationId", "=", dto.getApplicationId())
.and("id", "=", dto.getFlowId())
.and("status", "=", ApplicationFlowStatusEnum.TO_BE_AUDIT.getValue())
.and("delFlag", "=", false));
if (applicationFlow == null) {
throw new RuntimeException("待操作记录不存在");
}
Sys_user user = getCurrentUser();
if (user == null) {
throw new RuntimeException("用户信息过期,请重新登录");
}
// 先更新
applicationFlow.setStatus(dto.getStatus());
applicationFlow.setAuditor(user.getUsername());
applicationFlow.setAuditorId(user.getId());
applicationFlow.setRemark(dto.getRemark());
updateIgnoreNull(applicationFlow);
List<BrandApplicationFlow> flowList = dao().query(BrandApplicationFlow.class, Cnd.where("applicationId", "=", dto.getApplicationId()).and("delFlag", "=", false));
if (flowList == null || flowList.isEmpty()) {
throw new RuntimeException("流程记录为空");
}
long count = flowList.stream().filter(item -> Objects.equals(item.getStatus(), ApplicationFlowStatusEnum.PASSED.getValue())).count();
ApplicationFlowStatusEnum statusEnum = ApplicationFlowStatusEnum.getStatusEnumByValue(dto.getStatus());
//流程审批通过
if (statusEnum == ApplicationFlowStatusEnum.PASSED)
{
if (count == flowList.size()) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.PASSED.getCode()),
Cnd.where("id", "=", dto.getApplicationId()));
} else {
Optional<BrandApplicationFlow> first = flowList.stream().filter(item -> item.getSortNo() == (applicationFlow.getSortNo() + 1)).findFirst();
if (first.isPresent()) {
BrandApplicationFlow flow = first.get();
flow.setRemark(dto.getRemark());
flow.setStatus(ApplicationFlowStatusEnum.TO_BE_AUDIT.getValue());
updateIgnoreNull(flow);
}
}
} else if (statusEnum == ApplicationFlowStatusEnum.REPEAL) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.REPEAL.getCode()),
Cnd.where("id", "=", dto.getApplicationId()));
} else if (statusEnum == ApplicationFlowStatusEnum.RECALL) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.IN_REVIEW.getCode()),
Cnd.where("id", "=", dto.getApplicationId()));
} else if (statusEnum == ApplicationFlowStatusEnum.REJECT) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.REJECT.getCode()),
Cnd.where("id", "=", dto.getApplicationId()));
}
}
@Override
public void recall(BrandApplicationRecallDTO dto) {
String taskId = StrUtil.blankToDefault(dto.getProcessInstanceTaskId(), dto.getFlowId());
if (StrUtil.isNotBlank(taskId)) {
bpmService.revokeTask(taskId);
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.IN_REVIEW.getCode()),
Cnd.where("id", "=", dto.getApplicationId()));
return;
}
bpmService.revokeApply(dto.getApplicationId());
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.DRAFT.getCode()),
Cnd.where("id", "=", dto.getApplicationId()));
}
public void recallOld(BrandApplicationRecallDTO dto) {
// 撤回分为 申请人撤回 和 审核人撤回
// 如果是申请人,需要把 表单状态改为 DRAFT
Sys_user user = getCurrentUser();
// 查询 申请表单 在 flow 中的 curFlowId 和 nextFlowId
String applicationId = dto.getApplicationId();
List<BrandFlowUserVO> brandFlowUserVOS = selectApplicationTableVOById(applicationId);
if (user == null || brandFlowUserVOS.isEmpty()) {
throw new RuntimeException("撤回申请失败");
}
List<String> flowIds = brandFlowUserVOS.stream().map(BrandFlowUserVO::getFlowId).collect(Collectors.toList());
// 如果 申请人撤回 和 撤回发起人 为同一人,且在 申请模块撤销,就更新 申请表 为 DRAFT
// 如果是在 申请表中 撤回 时,dto.getFlowId() 是 null
boolean isSameUser = brandFlowUserVOS.get(0).getUserId().equals(user.getId()) && dto.getFlowId() == null;
if (isSameUser) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.DRAFT.getCode()),
Cnd.where("id", "=", applicationId));
dto.setFlowId(flowIds.get(0));
// 把所有 审批流程 状态改为 WAIT_AUDIT 0
updateStatusByApplicationId(applicationId, 0);
} else {
// 如果 是由上级人员撤回 审批 记录,且是在 审批表中操作, dto.getFlowId() 是 存在的
// 先修改 申请表 为 待审核状态
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.IN_REVIEW.getCode()),
Cnd.where("id", "=", applicationId));
// 再把 审批流程 中的状态改为撤回,下级状态改成待审核
int index = flowIds.indexOf(dto.getFlowId());
dto.setFlowId(flowIds.get(index));
Integer sortNo = brandFlowUserVOS.get(index).getSortNo();
// 把 高于当前 组织的 流状态 置为 0
updateStatusByApplicationId(applicationId, sortNo);
}
// 把当前记录改为 TO_BE_AUDIT
BrandApplicationFlow brandApplicationFlow = new BrandApplicationFlow();
brandApplicationFlow.setId(dto.getFlowId());
brandApplicationFlow.setAuditorId(user.getId());
brandApplicationFlow.setAuditor(user.getUsername());
brandApplicationFlow.setStatus(ApplicationFlowStatusEnum.TO_BE_AUDIT.getValue());
brandApplicationFlow.setRemark(isSameUser ? "申请人操作撤回" : "操作撤回");
updateIgnoreNull(brandApplicationFlow);
}
@Override
public List<BrandApplicationFlowVO> getFlowList(String applicationId) {
List<BrandApplicationFlow> applicationFlows = dao().query(BrandApplicationFlow.class,
Cnd.where("applicationId", "=", applicationId).and("delFlag", "=", false).asc("sortNo"));
return BrandApplicationFlowConvert.convertList(applicationFlows);
}
@Override
public void deleteByApplicationIds(List<String> ids) {
if (ids == null || ids.isEmpty()) {
return;
}
dao().update(BrandApplicationFlow.class, Chain.make("delFlag", true), Cnd.where("applicationId", "in", ids));
}
private void updateStatusByApplicationId(String applicationId, Integer sortNo) {
dao().update(BrandApplicationFlow.class,
Chain.make("status", 0).add("remark", "申请人操作撤回"),
Cnd.where("applicationId", "=", applicationId).and("sortNo", ">", sortNo));
}
private List<BrandFlowUserVO> selectApplicationTableVOById(String applicationId) {
List<BrandApplicationFlow> flows = dao().query(BrandApplicationFlow.class,
Cnd.where("applicationId", "=", applicationId).and("delFlag", "=", false).asc("sortNo"));
BrandStudioApplication application = dao().fetch(BrandStudioApplication.class, applicationId);
return flows.stream().map(flow -> {
BrandFlowUserVO vo = BeanUtil.copyProperties(flow, BrandFlowUserVO.class);
vo.setFlowId(flow.getId());
vo.setSortNo(flow.getSortNo());
vo.setUserId(application == null ? null : application.getUserId());
return vo;
}).collect(Collectors.toList());
}
private Sys_user getCurrentUser() {
String userId = SecurityUtil.getUserId();
if (userId == null || userId.isBlank()) {
return null;
}
return dao().fetch(Sys_user.class, userId);
}
}
@@ -0,0 +1,139 @@
package com.budwk.app.zhgh.brand.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.brand.domain.vo.ApplicationProgressVO;
import com.budwk.app.zhgh.brand.domain.vo.ApplicationWarnVO;
import com.budwk.app.zhgh.brand.domain.vo.UnionApplyStatsVO;
import com.budwk.app.zhgh.brand.models.BrandStudioApplication;
import com.budwk.app.zhgh.brand.service.BrandApplicationStatisticsService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.*;
import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class BrandApplicationStatisticsServiceImpl extends BaseServiceImpl<BrandStudioApplication> implements BrandApplicationStatisticsService {
public BrandApplicationStatisticsServiceImpl(Dao dao) {
super(dao);
}
// 管理员查看
@Override
public List<UnionApplyStatsVO> selectUnionApplyStats() {
Sql sql = Sqls.create("""
select unionId,
unionName,
createName as username,
count(1) as applyCnt,
sum(case when status = 5 then 1 else 0 end) as passedCnt,
round(sum(case when status = 5 then 1 else 0 end) * 100 / count(1), 2) as passRate,
sum(case when status = 1 then 1 else 0 end) as reviewCnt,
round(sum(case when status = 1 then 1 else 0 end) * 100 / count(1), 2) as reviewRate,
sum(case when status = 2 then 1 else 0 end) as rejectCnt,
round(sum(case when status = 2 then 1 else 0 end) * 100 / count(1), 2) as rejectRate,
sum(case when status = 3 then 1 else 0 end) as repealCnt,
round(sum(case when status = 3 then 1 else 0 end) * 100 / count(1), 2) as repealRate
from brand_studio_application
where delFlag = 0
group by unionId, unionName, createName
order by unionName
""");
return listVO(sql, UnionApplyStatsVO.class);
}
@Override
public List<ApplicationProgressVO> selectApplyProgressStats() {
Sql sql = Sqls.create("""
select bsa.id as applicationId,
bsa.name as applicationName,
bsa.unionId,
bsa.unionName,
bsa.userId,
bsa.status,
bsa.leadPeople,
bsa.createdAt as createTime,
count(baf.id) as totalNodes,
sum(case when baf.status = 5 then 1 else 0 end) as passedNodes,
case when count(baf.id) = 0 then 0 else round(sum(case when baf.status = 5 then 1 else 0 end) * 100 / count(baf.id), 2) end as progressPct
from brand_studio_application bsa
left join brand_application_flow baf on bsa.id = baf.applicationId and baf.delFlag = 0
where bsa.delFlag = 0
and bsa.userId = @userId
group by bsa.id, bsa.name, bsa.unionId, bsa.unionName, bsa.userId, bsa.status, bsa.leadPeople, bsa.createdAt
order by bsa.createdAt desc
""");
sql.setParam("userId", SecurityUtil.getUserId());
return listVO(sql, ApplicationProgressVO.class);
}
@Override
public List<ApplicationWarnVO> selectApplyWarningStats() {
Sql sql = Sqls.create("""
select bsa.id as applicationId,
bsa.name,
bsa.leadPeople,
bsa.status,
bsa.unionId,
bsa.unionName,
bsa.createdAt as receiveTime,
round((unix_timestamp(now()) * 1000 - bsa.createdAt) / 86400000, 2) as pendingDays,
case when (unix_timestamp(now()) * 1000 - bsa.createdAt) / 86400000 >= 7 then 1 else 0 end as warnStatus,
round(avg((unix_timestamp(now()) * 1000 - bsa.createdAt) / 86400000), 2) as avgAuditDays
from brand_studio_application bsa
where bsa.delFlag = 0
and bsa.status = 1
and (bsa.unionId = @unionId or @unionId = '')
group by bsa.id, bsa.name, bsa.leadPeople, bsa.status, bsa.unionId, bsa.unionName, bsa.createdAt
order by pendingDays desc
""");
sql.setParam("unionId", SecurityUtil.getUnionId());
return listVO(sql, ApplicationWarnVO.class);
}
// 对申请单的 type,年龄等等字段分组
public Map<String, Map<String, Long>> selectApplyMapCount() {
String userId = SecurityUtil.getUserId();
List<BrandStudioApplication> applications = dao().query(BrandStudioApplication.class,
Cnd.where("userId", "=", userId).and("delFlag", "=", false));
Map<String, Long> createYearCount = applications.stream()
.collect(Collectors.groupingBy(BrandStudioApplication::getCreateYear, Collectors.counting()))
.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey(
Comparator.comparingInt(year -> {
try {
return Integer.parseInt(year);
} catch (NumberFormatException e) {
return Integer.MAX_VALUE; // 非法年份放最后
}
})
))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
Map<String, Long> leadPeopleCount = applications.stream()
.collect(Collectors.groupingBy(BrandStudioApplication::getLeadPeople, Collectors.counting()));
Map<String, Long> leadAgeCount = applications.stream()
.collect(Collectors.groupingBy(app -> String.valueOf(app.getLeadAge()), Collectors.counting()));
Map<String, Long> typeCount = applications.stream()
.collect(Collectors.groupingBy(BrandStudioApplication::getType, Collectors.counting()));
Map<String, Long> trackCount = applications.stream()
.collect(Collectors.groupingBy(BrandStudioApplication::getTrack, Collectors.counting()));
Map<String, Map<String, Long>> mapMap = new HashMap<>();
mapMap.put("createYear", createYearCount);
mapMap.put("leadPeople", leadPeopleCount);
mapMap.put("leadAge", leadAgeCount);
mapMap.put("type", typeCount);
mapMap.put("track", trackCount);
return mapMap;
}
}
@@ -0,0 +1,446 @@
package com.budwk.app.zhgh.brand.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.BpmProcessConstant;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.bpm.enums.BpmProcessTaskStatusEnum;
import com.budwk.app.bpm.service.BpmService;
import com.budwk.app.sys.models.Sys_union;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.web.controllers.open.commons.service.CommonService;
import com.budwk.app.zhgh.brand.convert.BrandStudioApplicationConvert;
import com.budwk.app.zhgh.brand.convert.BrandStudioHonorConvert;
import com.budwk.app.zhgh.brand.convert.BrandStudioMemberConvert;
import com.budwk.app.zhgh.brand.domain.dto.*;
import com.budwk.app.zhgh.brand.domain.enums.BrandApplicationStatusEnums;
import com.budwk.app.zhgh.brand.domain.vo.*;
import com.budwk.app.zhgh.brand.models.BrandStudioApplication;
import com.budwk.app.zhgh.brand.models.BrandStudioHonor;
import com.budwk.app.zhgh.brand.models.BrandStudioMember;
import com.budwk.app.zhgh.brand.service.BrandApplicationFlowService;
import com.budwk.app.zhgh.brand.service.BrandStudioApplicationService;
import com.budwk.app.zhgh.brand.service.BrandStudioMemberService;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
@IocBean(args = {"refer:dao"})
public class BrandStudioApplicationServiceImpl extends BaseServiceImpl<BrandStudioApplication> implements BrandStudioApplicationService {
@Inject
private BrandStudioMemberService studioMemberService;
@Inject
private BrandApplicationFlowService flowService;
@Inject
private BpmService bpmService;
@Inject
private CommonService commonService;
public BrandStudioApplicationServiceImpl(Dao dao) {
super(dao);
}
// 分页查询申请表
@Override
public Pagination<BrandStudioApplicationVO> page(BrandStudioApplicationPageDTO pageDTO) {
String userId = SecurityUtil.getUserId();
Cnd cnd = Cnd.where("delFlag", "=", false);
/// 2. 先写“普通筛选”这一整组
if (StrUtil.isNotBlank(pageDTO.getName())) {
cnd.and("name", "like", "%" + pageDTO.getName() + "%");
}
if (StrUtil.isNotBlank(userId)) {
cnd.and("createdBy", "=", userId);
}
if (StrUtil.isNotBlank(pageDTO.getCreateYear())) {
cnd.and("createYear", "=", pageDTO.getCreateYear());
}
if (pageDTO.getStatus() != null) {
cnd.and("status", "=", pageDTO.getStatus());
}
if (StrUtil.isNotBlank(pageDTO.getLeadPeople())) {
cnd.and("leadPeople", "like", "%" + pageDTO.getLeadPeople() + "%");
}
cnd.desc("createYear");
cnd.desc("createdAt");
Pagination<BrandStudioApplication> page = listPage(pageDTO.getPageNumber(), pageDTO.getPageSize(), BrandStudioApplication.class, cnd);
List<BrandStudioApplicationVO> list = BrandStudioApplicationConvert.convertList(page.getList());
return new Pagination<>(page.getPageNo(), page.getPageSize(), page.getTotalCount(), list);
}
private Sys_user getCheckedUserDetail() {
Sys_user user = getCurrentUser();
if (user == null) {
throw new RuntimeException("用户信息过期,请重新登录");
}
if (StrUtil.isEmpty(SecurityUtil.getUnionId())) {
throw new RuntimeException("未关联工会");
}
return user;
}
@Override
public void save(BrandStudioApplicationCreateDTO dto) {
Sys_user user = getCheckedUserDetail();
BrandStudioApplication application = BrandStudioApplicationConvert.convert(dto);
application.setUserId(user.getId());
application.setDeptid(user.getUnitId());
application.setCreateName(user.getUsername());
Integer status = dto.getStatus();
BrandApplicationStatusEnums statusByCode = BrandApplicationStatusEnums.getStatusByCode(status);
if (statusByCode == null) {
throw new RuntimeException("状态异常");
}
application.setStatus(statusByCode.getCode());
insert(application);
insertBatchBrandStudioMember(dto.getBrandStudioMembers(), application.getId());
if (statusByCode == BrandApplicationStatusEnums.CREATING) {
/// 提交审核的话会记录在审核流表中
submitAudit(application, user);
}
}
/// 提交申请
@Override
public void submitApply(BrandApplicationAuditDTO dto) {
String applicationId = dto.getApplicationId();
BrandApplicationStatusEnums statusEnums = BrandApplicationStatusEnums.getStatusByCode(dto.getStatus());
if (statusEnums == null) {
throw new RuntimeException("提交申请失败");
}
Sys_user user = getCurrentUser();
if (user == null) {
throw new RuntimeException("用户信息过期,请重新登录");
}
/// 1.先更新工作室申请表的状态
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.CREATING.getCode()),
Cnd.where("id", "=", applicationId));
/// 2.插入待审批流程
if (statusEnums == BrandApplicationStatusEnums.IN_REVIEW) {
/// 提交审核的话会记录在审核流表中
BrandStudioApplication application = fetch(applicationId);
submitAudit(application, user);
}
}
private void submitAudit(BrandStudioApplication application, Sys_user user) {
if (application == null) {
throw new RuntimeException("提交申请失败");
}
String approvalLoginName = commonService.findUserLoginNameByRoleCode(RoleConstant.BRANCH_UNION_CHAIRMAN, Cnd.where(Sys_user_role::getUnionId, "=", application.getUnionId()));
if (StrUtil.isBlank(approvalLoginName)) {
throw new RuntimeException("分工会未配置审批人,请联系管理员");
}
try {
HashMap<String, Object> variables = new HashMap<>();
variables.put("applicationId", application.getId());
variables.put("status", BrandApplicationStatusEnums.IN_REVIEW.getCode());
variables.put("userId", user.getId());
bpmService.startSubmitProcessInstance(BpmProcessConstant.BRAND_STUDIO.name(), application.getName(), application.getId(), List.of(approvalLoginName), variables);
updateApplicationStatusToFail(application.getId(), true);
} catch (RuntimeException e) {
updateApplicationStatusToFail(application.getId(), false);
throw e;
}
}
private void updateApplicationStatusToFail(String businessId, Boolean success) {
if (success) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.IN_REVIEW.getCode()),
Cnd.where("id", "=", businessId));
} else {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.FAIL.getCode()),
Cnd.where("id", "=", businessId));
}
}
private void insertBatchBrandStudioMember(List<BrandStudioMemberCreateDTO> memberCreateDTOS, String id) {
List<BrandStudioMember> brandStudioMembers = BrandStudioMemberConvert.convertList(memberCreateDTOS);
if (brandStudioMembers != null && !brandStudioMembers.isEmpty()) {
brandStudioMembers.forEach(member -> member.setApplicationId(id));
dao().insert(brandStudioMembers);
}
}
@Override
public void oaAuditUpdateStatus(String id, Integer status, String reason) {
this.update(Chain.make("status", status), Cnd.where("id", "=", id));
}
@Override
public void update(BrandStudioApplicationUpdateDTO dto) {
BrandStudioApplication entity = BrandStudioApplicationConvert.convert(dto);
updateIgnoreNull(entity);
/// 先删除成员信息
List<String> ids = Collections.singletonList(dto.getId());
((BrandStudioMemberServiceImpl) studioMemberService).deleteByApplicationIds(ids);
BrandApplicationStatusEnums statusByCode = BrandApplicationStatusEnums.getStatusByCode(dto.getStatus());
if (statusByCode == null) {
throw new RuntimeException("状态异常");
}
/// 再插入新的成员信息
insertBatchBrandStudioMember(dto.getBrandStudioMembers(), dto.getId());
if (statusByCode == BrandApplicationStatusEnums.CREATING) {
/// 提交审核的话会记录在审核流表中
submitAudit(fetch(dto.getId()), getCheckedUserDetail());
}
}
@Override
public void updateMemberOrHonor(BrandStudioApplicationUpdateDTO dto) {
dao().update(BrandStudioApplication.class, Chain.make("memberNum", dto.getMemberNum()), Cnd.where("id", "=", dto.getId()));
/// 先删除成员信息
List<String> ids = Collections.singletonList(dto.getId());
((BrandStudioMemberServiceImpl) studioMemberService).deleteByApplicationIds(ids);
/// 再插入新的成员信息
insertBatchBrandStudioMember(dto.getBrandStudioMembers(), dto.getId());
/// 修改荣誉信息
dao().update(BrandStudioHonor.class, Chain.make("delFlag", true), Cnd.where("applicationId", "in", ids));
List<BrandStudioHonorCreateDTO> honorCreateDTOS = dto.getBrandStudioHonors();
List<BrandStudioHonor> honors = BrandStudioHonorConvert.convertListDTO(honorCreateDTOS);
if (honors != null && !honors.isEmpty()) {
honors.forEach(honor -> honor.setApplicationId(dto.getId()));
dao().insert(honors);
}
}
@Override
public void delete(List<String> ids) {
String userId = SecurityUtil.getUserId();
List<String> validIds = dao().query(BrandStudioApplication.class, Cnd.where("id", "in", ids)
.and("userId", "=", userId)
.and("delFlag", "=", false))
.stream().map(BrandStudioApplication::getId).collect(toList());
Set<String> inputSet = new HashSet<>(ids);
Set<String> validSet = new HashSet<>(validIds);
if (!validSet.equals(inputSet)) {
throw new RuntimeException("存在不可操作数据,删除失败");
}
dao().update(BrandStudioApplication.class, Chain.make("delFlag", true), Cnd.where("id", "in", validIds));
((BrandStudioMemberServiceImpl) studioMemberService).deleteByApplicationIds(validIds);
flowService.deleteByApplicationIds(validIds);
}
@Override
public SysUnionDeptVO queryUserUnion() {
Sys_user user = getCurrentUser();
if (user == null) {
throw new RuntimeException("用户信息过期,请重新登录");
}
String unionId = SecurityUtil.getUnionId();
if (StrUtil.isEmpty(unionId)) {
throw new RuntimeException("未关联工会");
}
/// 获取用户所属公会和机构名
SysUnionDeptVO sysUnionDeptVO = new SysUnionDeptVO();
sysUnionDeptVO.setUserId(user.getId());
sysUnionDeptVO.setUnionId(unionId);
Sys_union union = dao().fetch(Sys_union.class, unionId);
if (union != null) {
sysUnionDeptVO.setUnionName(union.getName());
sysUnionDeptVO.setTopUnionId(union.getId());
sysUnionDeptVO.setTopUnionName(union.getName());
}
sysUnionDeptVO.setDeptId(user.getUnitId());
Sys_unit unit = dao().fetch(Sys_unit.class, user.getUnitId());
sysUnionDeptVO.setDeptName(unit == null ? null : unit.getName());
return sysUnionDeptVO;
}
@Override
public List<LeaderHonorGroupVO> queryLeaderHonor() {
List<LeaderHonorVO> leaderHonorVOS = selectLeaderHonor();
Map<String, List<LeaderHonorVO>> grouped = leaderHonorVOS.stream()
.collect(Collectors.groupingBy(LeaderHonorVO::getEmplid));
return grouped.entrySet().stream()
.map(entry -> {
String emplid = entry.getKey();
List<LeaderHonorVO> list = entry.getValue();
// 假设同一 emplid 的 userName 相同,取第一个
String userName = list.get(0).getUserName();
Integer age = list.get(0).getAge();
List<String> honorNames = list.stream()
.map(LeaderHonorVO::getHonorName)
.collect(Collectors.toList());
return new LeaderHonorGroupVO(emplid, userName, age, honorNames);
})
.collect(Collectors.toList());
}
@Override
public BrandStudioApplicationVO getDetailById(String id) {
BrandStudioApplication entity = fetch(id);
if (entity == null || Boolean.TRUE.equals(entity.getDelFlag())) {
throw new RuntimeException("未查询到该申请记录");
}
BrandStudioApplicationVO applicationVO = BrandStudioApplicationConvert.convert(entity);
List<BrandStudioMember> brandStudioMembers = dao().query(BrandStudioMember.class, Cnd.where("applicationId", "=", id).and("delFlag", "=", false));
if (brandStudioMembers != null && !brandStudioMembers.isEmpty()) {
List<BrandStudioMemberVO> brandStudioMembersVO = BrandStudioMemberConvert.convertListVO(brandStudioMembers);
applicationVO.setBrandStudioMembers(brandStudioMembersVO);
}
List<BrandStudioHonor> honorList = dao().query(BrandStudioHonor.class, Cnd.where("applicationId", "=", id).and("delFlag", "=", false));
if (honorList != null && !honorList.isEmpty()) {
List<BrandStudioHonorVO> honorVOS = BrandStudioHonorConvert.convertListVO(honorList);
applicationVO.setBrandStudioHonors(honorVOS);
}
/// 查询当前工作流待审核 ID
return applicationVO;
}
@Override
public void archive(List<String> idList) {
if (idList == null || idList.isEmpty()) {
return;
}
dao().update(BrandStudioApplication.class, Chain.make("archive", 1), Cnd.where("id", "in", idList));
}
/// 之前的工作流逻辑,已废弃
@Override
public Pagination<BrandStudioApplyFlowVO> auditPage(BrandStudioApplicationPageDTO pageDTO) {
return flowAuditPage(pageDTO);
}
/// 新的工作流逻辑
@Override
public Pagination<BrandStudioApplyFlowVO> flowAuditPage(BrandStudioApplicationPageDTO dto) {
Sql sql = Sqls.create("""
select bsa.*, inst.id as processInstanceId, inst.processInstanceNodeName as processTaskNode,
task.id as flowId, task.id as processInstanceTaskId, task.taskStatus as processTaskReason,
task.createdOn as processTaskStartTime, task.endOn as processTaskEndTime
from brand_studio_application bsa
inner join bpm_process_instance inst on bsa.id = inst.processInstanceBusinessId
inner join bpm_process_task task on inst.id = task.processInstanceId and task.delFlag = 0
inner join bpm_process_define def on inst.processDefineId = def.id and def.code = 'BRAND_STUDIO'
where bsa.delFlag = 0
$condition
order by task.createdOn desc
""");
Cnd cnd = Cnd.NEW();
if (StrUtil.isNotBlank(dto.getName())) {
cnd.and("bsa.name", "like", "%" + dto.getName() + "%");
}
if (StrUtil.isNotBlank(dto.getCreateYear())) {
cnd.and("bsa.createYear", "=", dto.getCreateYear());
}
if (StrUtil.isNotBlank(dto.getLeadPeople())) {
cnd.and("bsa.leadPeople", "like", "%" + dto.getLeadPeople() + "%");
}
if (dto.getProcessStatus() != null) {
cnd.and("task.taskStatus", "=", getProcessTaskStatus(dto.getProcessStatus()));
}
if (dto.getStatus() != null) {
cnd.and("bsa.status", "=", dto.getStatus());
}
sql.setCondition(cnd);
return listPageVO(dto, sql, BrandStudioApplyFlowVO.class);
}
private String getProcessTaskStatus(Integer processStatus) {
if (Objects.equals(processStatus, 1)) {
return BpmProcessTaskStatusEnum.ACTIVE.name();
}
if (Objects.equals(processStatus, 5)) {
return BpmProcessTaskStatusEnum.COMPLETE.name();
}
return String.valueOf(processStatus);
}
@Override
public Pagination<BrandStudioApplicationHonorVO> summaryPage(BrandStudioApplicationPageDTO pageDTO) {
Sql sql = Sqls.create("""
select bsa.*, bsh.id as honorId, bsh.honorName, bsh.honorGrade, bsh.requireYear, bsh.honorCategory
from brand_studio_application bsa
left join brand_studio_honor bsh on bsa.id = bsh.applicationId and bsh.delFlag = 0
where bsa.delFlag = 0
$condition
order by bsa.createYear desc, bsa.createdAt desc
""");
Cnd cnd = Cnd.NEW();
if (StrUtil.isNotBlank(pageDTO.getName())) {
cnd.and("bsa.name", "like", "%" + pageDTO.getName() + "%");
}
if (StrUtil.isNotBlank(pageDTO.getCreateYear())) {
cnd.and("bsa.createYear", "=", pageDTO.getCreateYear());
}
if (StrUtil.isNotBlank(pageDTO.getLeadPeople())) {
cnd.and("bsa.leadPeople", "like", "%" + pageDTO.getLeadPeople() + "%");
}
if (pageDTO.getStatus() != null) {
cnd.and("bsa.status", "=", pageDTO.getStatus());
}
sql.setCondition(cnd);
return listPageVO(pageDTO, sql, BrandStudioApplicationHonorVO.class);
}
@Override
public List<BrandStudioApplicationHonorVO> getHonorDetailById(String id) {
List<BrandStudioHonor> brandStudioHonors = dao().query(BrandStudioHonor.class, Cnd.where("applicationId", "=", id).and("delFlag", "=", false));
return BrandStudioHonorConvert.convertList(brandStudioHonors);
}
@Override
public void export() {
throw new RuntimeException("当前项目未配置品牌工作室台账导出模板");
}
@Override
public void exportDocx(String id, HttpServletResponse response) {
throw new RuntimeException("当前项目未配置品牌工作室申请文档模板");
}
private List<LeaderHonorVO> selectLeaderHonor() {
Sql sql = Sqls.create("""
select su.id,
su.loginname as emplid,
su.username as userName,
su.unitId as deptid,
su.unitId,
su.birthday,
su.professionalTitle as honorName,
u.unionId as unionId
from sys_user su
left join sys_unit u on su.unitId = u.id
where su.delFlag = 0
and su.disabled = 0
""");
return listVO(sql, LeaderHonorVO.class);
}
private Sys_user getCurrentUser() {
String userId = SecurityUtil.getUserId();
if (userId == null || userId.isBlank()) {
return null;
}
return dao().fetch(Sys_user.class, userId);
}
}
@@ -0,0 +1,16 @@
package com.budwk.app.zhgh.brand.service.impl;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.brand.models.BrandStudioHonor;
import com.budwk.app.zhgh.brand.service.BrandStudioHonorService;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
@IocBean(args = {"refer:dao"})
public class BrandStudioHonorServiceImpl extends BaseServiceImpl<BrandStudioHonor> implements BrandStudioHonorService {
public BrandStudioHonorServiceImpl(Dao dao) {
super(dao);
}
}
@@ -0,0 +1,49 @@
package com.budwk.app.zhgh.brand.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioMemberCreateDTO;
import com.budwk.app.zhgh.brand.domain.dto.BrandStudioMemberUpdateDTO;
import com.budwk.app.zhgh.brand.models.BrandStudioMember;
import com.budwk.app.zhgh.brand.service.BrandStudioMemberService;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.List;
@IocBean(args = {"refer:dao"})
public class BrandStudioMemberServiceImpl extends BaseServiceImpl<BrandStudioMember> implements BrandStudioMemberService {
public BrandStudioMemberServiceImpl(Dao dao) {
super(dao);
}
@Override
public void save(BrandStudioMemberCreateDTO dto) {
BrandStudioMember member = BeanUtil.copyProperties(dto, BrandStudioMember.class);
insert(member);
}
@Override
public void update(BrandStudioMemberUpdateDTO dto) {
BrandStudioMember member = BeanUtil.copyProperties(dto, BrandStudioMember.class);
updateIgnoreNull(member);
}
@Override
public void delete(List<String> ids) {
if (ids == null || ids.isEmpty()) {
return;
}
dao().update(BrandStudioMember.class, Chain.make("delFlag", true), Cnd.where("id", "in", ids));
}
public void deleteByApplicationIds(List<String> applicationIds) {
if (applicationIds == null || applicationIds.isEmpty()) {
return;
}
dao().update(BrandStudioMember.class, Chain.make("delFlag", true), Cnd.where("applicationId", "in", applicationIds));
}
}
@@ -0,0 +1,24 @@
import request from '@/utils/request';
/**
* 根据id查询流程列表
*/
export async function getBrandApplyFlowListApi(params) {
return await request.get('/act/application/flow/process', {
params: params
});
}
/**
* 审核
*/
export async function auditApplicationApi(data) {
return await request.put('/act/application/flow/audit', data);
}
/**
* 撤回
*/
export async function recallApplicationApi(data) {
return await request.put('/act/application/flow/recall', data);
}
@@ -0,0 +1,92 @@
import request from '@/utils/request';
/**
* 查询申请人工会和机构
*/
export async function getUserBelongUnionApi(params) {
return request.get('/act/brand/application/union', { params });
}
/**
* 查询领衔人和荣誉
*/
export async function getLeaderHonorApi(params) {
return request.get('/act/brand/application/leader', { params });
}
/**
* 分页查询品牌工作室列表
*/
export async function getBrandStudioApplicationPageApi(params) {
return request.get('/act/brand/application/page', { params });
}
/**
* 根据id查询品牌工作室申请信息
*/
export async function getBrandStudioApplicationApi(id) {
return await request.get('/act/brand/application/' + id);
}
/**
* 添加品牌工作室
*/
export async function addBrandStudioApplicationApi(data) {
return await request.post('/act/brand/application', data);
}
/**
* 修改品牌工作室信息
*/
export async function updateBrandStudioApplicationApi(data) {
return await request.put('/act/brand/application', data);
}
/**
* 修改品牌工作室基础信息
*/
export async function updateBrandStudioBaseInfoApi(data) {
return await request.put('/act/brand/application/base', data);
}
/**
* 修改品牌工作室成员和荣誉信息
*/
export async function updateBrandStudioMemberOrHonorApi(data) {
return await request.post('/act/brand/application/honor', data);
}
/**
* 批量删除品牌工作室
*/
export async function removeBrandStudioApplicationsApi(ids) {
return await request.post('/act/brand/application/delete', { data: ids });
}
/**
* 分页查询品牌工作室 待审核 列表
*/
export async function getBrandApplicationAuditPageApi(params) {
return request.get('/act/brand/application/auditPage', { params });
}
/**
* 分页查询品牌工作室+荣誉列表
*/
export async function getBrandStudioApplicationHonorPageApi(params) {
return request.get('/act/brand/application/summaryPage', { params });
}
/**
* 根据id查询品牌工作室荣誉信息
*/
export async function getBrandStudioHonorApi(id) {
return await request.get('/act/brand/application/honor/' + id);
}
/**
* 提交申请
*/
export async function submitApplicationApi(data) {
return await request.put('/act/brand/application/submitApply', data);
}
@@ -0,0 +1,37 @@
import request from '@/utils/request';
/**
* 查询工会审批统计表单
*/
export async function getUnionApplyStatsApi(params) {
return await request.get('/act/application/statistics/unionStats', {
params: params
});
}
/**
* 各申请表单进度情况
*/
export async function getApplyProgressStatsApi(params) {
return await request.get('/act/application/statistics/applyProgress', {
params: params
});
}
/**
* 工会申请预警
*/
export async function getApplyWarningStatsApi(params) {
return await request.get('/act/application/statistics/applyWarning', {
params: params
});
}
/**
* 统计用户申请的各字段分组
*/
export async function getApplyCountStatsApi(params) {
return await request.get('/act/application/statistics/applyCount', {
params: params
});
}
@@ -0,0 +1,91 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<el-form :inline="true" :model="pageForm">
<el-form-item label="工作室名称">
<el-input v-model="pageForm.name" clearable></el-input>
</el-form-item>
<el-form-item label="创建年份">
<el-date-picker v-model="pageForm.createYear" type="year" value-format="yyyy"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="pageData">查询</el-button>
<el-button @click="openApply()">新建</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" border>
<el-table-column type="index" label="序号" width="60"></el-table-column>
<el-table-column prop="name" label="工作室名称" min-width="160"></el-table-column>
<el-table-column prop="createYear" label="创立年度" width="100"></el-table-column>
<el-table-column prop="leadPeople" label="领衔人" width="120"></el-table-column>
<el-table-column prop="deptName" label="所属机构" min-width="140"></el-table-column>
<el-table-column prop="type" label="工作室类型" width="120"></el-table-column>
<el-table-column prop="track" label="工作室赛道" width="120"></el-table-column>
<el-table-column prop="memberNum" label="成员人数" width="90"></el-table-column>
<el-table-column label="状态" width="100">
<template slot-scope="{row}">
<el-tag v-if="row.status===0" type="info">草稿</el-tag>
<el-tag v-else-if="row.status===1" type="warning">审核中</el-tag>
<el-tag v-else-if="row.status===2" type="danger">驳回</el-tag>
<el-tag v-else-if="row.status===5" type="success">通过</el-tag>
<el-tag v-else>{{row.status}}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="{row}">
<el-button type="text" @click="openApply(row.id)">编辑</el-button>
<el-button type="text" @click="submitApply(row)" v-if="row.status===0 || row.status===7">申请</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
pageForm: {
page: 1,
limit: 20,
createYear: String(new Date().getFullYear())
},
tableData: []
}
},
methods: {
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
pageData() {
this.$axios.get('/platform/zhgh/brand/application/page', {params: this.pageForm}).then((res) => {
const data = this.getData(res) || {}
this.tableData = data.list || data.data || []
})
},
openApply(id) {
window.location.href = '/platform/zhgh/brand/apply/index' + (id ? '?id=' + id : '')
},
submitApply(row) {
this.$axios.post('/platform/zhgh/brand/application/submitApply', {
applicationId: row.id,
status: 1
}).then(() => {
this.$message.success('申请成功')
this.pageData()
})
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,416 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<param-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
highlight-current-row
:export-config="{ fileName: '参数设置' }"
cache-key="systemConfigTable"
:tools="false"
>
<template #toolbar>
<el-button
type="primary"
class="ele-btn-icon"
:icon="PlusOutlined"
v-permission="'system:config:add'"
@click="openEdit()"
>
新建
</el-button>
<el-button
type="danger"
class="ele-btn-icon hidden-sm-and-down"
:icon="DeleteOutlined"
v-permission="'system:config:remove'"
@click="removeBatch()"
>
删除
</el-button>
</template>
<template #createTime="{ row }">
{{ dayjs(row.createTime).format('YYYY-MM-DD') }}
</template>
<template #leadIdentity="{ row }">
<div
style="display: flex"
v-for="(item, index) in JSON.parse(row.leadIdentity)"
:key="index"
>
<el-tag type="primary">
{{ item.dictLabel }}
</el-tag>
</div>
</template>
<template #memberNum="{ row }">
{{ row.memberNum + '人' }}
</template>
<template #status="{ row }">
<el-tag style="cursor: pointer" v-if="row.status === 0" type="info">
草稿箱
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.status === 1"
type="warning"
>
审核中
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 2" type="danger">
驳回
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 4" type="info">
取消
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.status === 5"
type="success"
>
通过
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 6">
正在创建
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 7">
创建失败
</el-tag>
</template>
<template #action="{ row }">
<el-link
v-if="[0, 2, 4].includes(row.status)"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="openEdit(row)"
:icon="EditOutlined"
>
修改
</el-link>
<el-divider
v-if="[2, 4].includes(row.status)"
v-permission="['system:config:edit', 'system:config:remove']"
direction="vertical"
/>
<el-link
v-if="row.status !== 0"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click.stop="handleProcessDetail(row)"
:icon="View"
:disabled="row.status === 6 || row.status === 7"
>
查看
</el-link>
<el-divider
v-if="row.status === 0"
v-permission="['system:config:edit', 'system:config:remove']"
direction="vertical"
/>
<el-link
v-if="row.status === 0 || row.status === 7"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="submitApply(row)"
:icon="FileOutlined"
>
申请
</el-link>
<!-- <el-divider
v-if="row.status !== 0 && row.status !== 2"
v-permission="['system:config:edit', 'system:config:remove']"
direction="vertical"
/>
<el-link
v-if="row.status !== 0 && row.status !== 2"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="exportDocx(row.id)"
:icon="DownloadOutlined"
>
导出
</el-link>-->
</template>
</ele-pro-table>
</ele-card>
</ele-page>
</template>
<script setup>
import { computed, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import { EleMessage } from 'ele-admin-plus';
import {
DeleteOutlined,
EditOutlined,
PlusOutlined
} from '@/components/icons';
import ParamSearch from '../components/param-search.vue';
import dayjs from 'dayjs';
import {
getBrandStudioApplicationPageApi,
removeBrandStudioApplicationsApi
} from '@/api/office/brand/index.js';
import { useRouter } from 'vue-router';
import { BRAND_APPLY_PATH } from '@/config/setting.js';
const { push } = useRouter();
import { usePageTab } from '@/utils/use-page-tab';
import { View } from '@element-plus/icons-vue';
import $enums from '@/utils/enums.js';
import { FileOutlined, DownloadOutlined } from '@/components/icons/index.js';
import { submitApplicationApi } from '@/api/office/brand/index.js';
import request from '@/utils/request.js';
const { addPageTab } = usePageTab();
defineOptions({ name: 'Application' });
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => {
return [
{
type: 'selection',
columnKey: 'selection',
width: 50,
align: 'center'
},
{
type: 'index',
columnKey: 'index',
width: 60,
align: 'center',
label: '序号'
},
{
prop: 'name',
label: '工作室名称',
align: 'center',
minWidth: 180
},
{
prop: 'createYear',
label: '创立年度',
align: 'center',
minWidth: 80,
slot: 'createYear'
},
{
prop: 'leadPeople',
label: '领衔人',
minWidth: 100,
align: 'center',
slot: 'leadPeople'
},
{
prop: 'deptName',
label: '所属单位机构',
minWidth: 120,
align: 'center'
},
{
prop: 'type',
label: '工作室类型',
align: 'center',
minWidth: 110,
slot: 'type'
},
{
prop: 'track',
label: '工作室赛道',
align: 'center',
minWidth: 110,
slot: 'track'
},
{
prop: 'memberNum',
label: '成员人数',
align: 'center',
minWidth: 80,
slot: 'memberNum'
},
{
prop: 'address',
label: '工作室地址',
minWidth: 140,
align: 'center',
slot: 'address'
},
{
prop: 'createTime',
label: '申请时间',
minWidth: 100,
align: 'center',
slot: 'createTime'
},
{
prop: 'status',
label: '状态',
minWidth: 80,
align: 'center',
slot: 'status'
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
];
});
/** 表格选中数据 */
const selections = ref([]);
/** 当前编辑数据 */
const current = ref(null);
/** 表格数据源 */
const datasource = async ({ pages, where, filters }) => {
where = {
...where,
createYear: new Date().getFullYear().toString()
};
const { data } = await getBrandStudioApplicationPageApi({
...where,
...filters,
...pages
});
return data;
};
/** 搜索 */
const reload = (where) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 打开编辑弹窗 */
const openEdit = (row) => {
current.value = row ?? null;
const path = row ? BRAND_APPLY_PATH + '/' + row.id : BRAND_APPLY_PATH;
addPageTab({
title: row ? `修改申请` : '创建申请',
key: path,
closable: true,
meta: { icon: 'LinkOutlined' }
});
push(path);
};
/** 批量删除 */
const removeBatch = (row) => {
const rows = row == null ? selections.value : [row];
if (!rows.length) {
EleMessage.error({ message: '请至少选择一条数据', plain: true });
return;
}
ElMessageBox.confirm(
`是否确认删除"${rows.map((d) => d.name).join()}"工作室?`,
'系统提示',
{ type: 'warning', draggable: true }
)
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
console.log(rows);
removeBrandStudioApplicationsApi(rows.map((d) => d.id))
.then(() => {
loading.close();
EleMessage.success({ message: '删除成功', plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
// 提交申请 id
const submitApply = (row) => {
console.log('submitApply');
ElMessageBox.confirm(`确认申请「${row.name}」工作室?`, '系统提示', {
type: 'warning',
draggable: true
})
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
const params = {
applicationId: row.id,
status: 1
};
submitApplicationApi(params)
.then(() => {
loading.close();
EleMessage.success({ message: '申请成功', plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
/** 审批进度 */
const handleProcessDetail = (row) => {
push({
name: 'WorkflowProcessInstanceDetail',
query: {
businessId: row.id,
businessType:
$enums.BUSINESS_PROCESS_KEY_NUMBER.BRAND_OFFICE_USER_APPLY,
processFormPath: encodeURI('/brand/components/flow-view.vue')
}
});
};
import { saveAs } from 'file-saver';
/** 导出申请表格文档 */
const exportDocx = async (id) => {
const token = localStorage.getItem('token');
const res = await request({
url: `/act/brand/application/export/docx/${id}`,
method: 'POST',
responseType: 'blob',
headers: { Authorization: token }
});
console.log('OK res=', res);
// 处理返回的Blob数据
const blob = new Blob([res.data], {
type: res.data.type || 'application/docx'
});
const fileName = `上海浦东发展银行个人品牌工作室推荐表.docx`;
saveAs(blob, fileName, null);
};
</script>
@@ -0,0 +1,411 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<param-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
highlight-current-row
:export-config="{ fileName: '参数设置' }"
cache-key="systemConfigTable"
:tools="false"
>
<template #toolbar>
<el-button
type="primary"
class="ele-btn-icon"
:icon="PlusOutlined"
v-permission="'system:config:add'"
@click="openEdit()"
>
新建
</el-button>
<el-button
type="danger"
class="ele-btn-icon hidden-sm-and-down"
:icon="DeleteOutlined"
v-permission="'system:config:remove'"
@click="removeBatch()"
>
删除
</el-button>
</template>
<template #createTime="{ row }">
{{ dayjs(row.createTime).format('YYYY-MM-DD') }}
</template>
<template #leadIdentity="{ row }">
<div
style="display: flex"
v-for="(item, index) in JSON.parse(row.leadIdentity)"
:key="index"
>
<el-tag type="primary">
{{ item.dictLabel }}
</el-tag>
</div>
</template>
<template #memberNum="{ row }">
{{ row.memberNum + '人' }}
</template>
<template #status="{ row }">
<el-tag style="cursor: pointer" v-if="row.status === 0" type="info">
草稿箱
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.status === 1"
type="warning"
>
审核中
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 2" type="danger">
驳回
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 3" type="info">
撤销
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.status === 5"
type="success"
>
通过
</el-tag>
</template>
<template #action="{ row }">
<el-link
v-if="row.status !== 0"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="showFlow(row)"
:icon="FileOutlined"
>
查看
</el-link>
<el-divider
v-if="![0, 3, 5].includes(row.status)"
v-permission="['system:config:edit', 'system:config:remove']"
direction="vertical"
/>
<el-link
v-if="row.status === 0 || row.status === 2"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="openEdit(row)"
:icon="EditOutlined"
>
修改
</el-link>
<el-divider
v-if="row.status === 0"
v-permission="['system:config:edit', 'system:config:remove']"
direction="vertical"
/>
<el-link
v-if="row.status === 0"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="submitApply(row)"
:icon="FileOutlined"
>
申请
</el-link>
<el-link
v-if="row.status === 1"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click.stop="handleReturn(row)"
:icon="ReloadOutlined"
>
撤回
</el-link>
</template>
</ele-pro-table>
</ele-card>
<flow-audit
v-model="showFlowCurrent"
:data="current"
:is-audit="false"
@done="reload"
/>
</ele-page>
</template>
<script setup>
import { computed, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import {EleMessage} from 'ele-admin-plus';
import {
DeleteOutlined,
EditOutlined,
PlusOutlined
} from '@/components/icons';
import ParamSearch from '../components/param-search.vue';
import dayjs from 'dayjs';
import {
getBrandStudioApplicationPageApi,
removeBrandStudioApplicationsApi
} from '@/api/office/brand/index.js';
import { FileOutlined, ReloadOutlined } from '@/components/icons/index.js';
import {
recallApplicationApi,
} from '@/api/office/application/index.js';
import FlowAudit from '@/views/brand/components/flow-audit.vue';
import { useRouter } from 'vue-router';
import { BRAND_APPLY_PATH } from '@/config/setting.js';
const { push } = useRouter();
import { usePageTab } from '@/utils/use-page-tab';
const { addPageTab } = usePageTab();
defineOptions({ name: 'Application' });
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => {
return [
{
type: 'selection',
columnKey: 'selection',
width: 50,
align: 'center'
},
{
type: 'index',
columnKey: 'index',
width: 60,
align: 'center',
label: '序号'
},
{
prop: 'name',
label: '工作室名称',
align: 'center',
minWidth: 180
},
{
prop: 'createYear',
label: '创立年度',
align: 'center',
minWidth: 80,
slot: 'createYear'
},
{
prop: 'leadPeople',
label: '领衔人',
minWidth: 100,
align: 'center',
slot: 'leadPeople'
},
{
prop: 'deptName',
label: '所属单位机构',
minWidth: 120,
align: 'center'
},
{
prop: 'type',
label: '工作室类型',
align: 'center',
minWidth: 110,
slot: 'type'
},
{
prop: 'track',
label: '工作室赛道',
align: 'center',
minWidth: 110,
slot: 'track'
},
{
prop: 'memberNum',
label: '成员人数',
align: 'center',
minWidth: 80,
slot: 'memberNum'
},
{
prop: 'address',
label: '工作室地址',
minWidth: 140,
align: 'center',
slot: 'address'
},
{
prop: 'createTime',
label: '申请时间',
minWidth: 100,
align: 'center',
slot: 'createTime'
},
{
prop: 'status',
label: '状态',
minWidth: 80,
align: 'center',
slot: 'status'
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
];
});
/** 表格选中数据 */
const selections = ref([]);
/** 当前编辑数据 */
const current = ref(null);
/** 表格数据源 */
const datasource = async ({ pages, where, filters }) => {
const { data } = await getBrandStudioApplicationPageApi({
...where,
...filters,
...pages
});
return data;
};
/** 搜索 */
const reload = (where) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 打开编辑弹窗 */
const openEdit = (row) => {
current.value = row ?? null;
const path = row ? BRAND_APPLY_PATH + '/' + row.id : BRAND_APPLY_PATH;
addPageTab({
title: row ? `修改申请` : '创建申请',
key: path,
closable: true,
meta: { icon: 'LinkOutlined' }
});
push(path);
};
/** 批量删除 */
const removeBatch = (row) => {
const rows = row == null ? selections.value : [row];
if (!rows.length) {
EleMessage.error({ message: '请至少选择一条数据', plain: true });
return;
}
ElMessageBox.confirm(
`是否确认删除"${rows.map((d) => d.name).join()}"工作室?`,
'系统提示',
{ type: 'warning', draggable: true }
)
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
console.log(rows);
removeBrandStudioApplicationsApi(rows.map((d) => d.id))
.then(() => {
loading.close();
EleMessage.success({ message: '删除成功', plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
const showFlowCurrent = ref(false);
//查看流程列表
const showFlow = (row) => {
console.log('showFlow');
current.value = row ?? null;
showFlowCurrent.value = true;
};
// 用户撤回申请
const handleReturn = (row) => {
console.log('handleReturn');
console.log(row);
ElMessageBox.confirm(`确认撤回"${row.name}"工作室申请吗?`, '系统提示', {
type: 'warning',
draggable: true
})
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
const params = {
applicationId: row.id
};
recallApplicationApi(params)
.then(() => {
loading.close();
EleMessage.success({ message: '撤回成功', plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
// 提交申请 id
const submitApply = (row) => {
console.log('submitApply');
ElMessageBox.confirm(`确认申请"${row.name}"工作室?`, '系统提示', {
type: 'warning',
draggable: true
})
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
const params = {
applicationId: row.id,
status: 1
};
submitApplicationApi(params)
.then(() => {
loading.close();
EleMessage.success({ message: '申请成功', plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
</script>
@@ -0,0 +1,126 @@
<script setup>
import { onMounted, ref, watch } from 'vue';
import { BRAND_APPLICATION_PATH } from '@/config/setting';
import {
getBrandStudioApplicationApi,
getUserBelongUnionApi,
} from '@/api/office/brand/index.js';
import { useRoute, useRouter } from 'vue-router';
import TableInfo from "@/views/brand/components/table-info.vue";
const { push } = useRouter();
const route = useRoute();
const childRef = ref()
/** 是否是修改:针对整体 */
const isUpdate = ref(false);
/** 提交状态 */
const loading = ref(false);
const unionId = ref('');
const topUnionId = ref('');
const handleCancel = () => {
push(BRAND_APPLICATION_PATH);
};
const save = (saveType) => {
loading.value = true;
// 调用子组件进行数据提交
childRef.value.submit({
saveType: saveType,
isUpdate: isUpdate.value
})
};
// 编辑结束回调
const editFinish = () => {
push(BRAND_APPLICATION_PATH);
}
const brandForm = ref({})
const queryInfoById = (id) => {
loading.value = true
getBrandStudioApplicationApi(id)
.then(({ data }) => {
console.log(data)
brandForm.value = data
})
.catch((err) => {
console.log(err);
})
.finally(() => {
loading.value = false
});
};
const userUnionInfo = ref({})
// 获取成员当前所在工会
const loadUserUnion = async () => {
const { data } = await getUserBelongUnionApi();
unionId.value = data.unionId;
topUnionId.value = data.topUnionId;
userUnionInfo.value = data
}
onMounted(async () => {
console.log('mounted');
await loadUserUnion();
});
watch(
() => route.params.id,
() => {
const id = route.params.id;
if (id) {
queryInfoById(id);
isUpdate.value = true;
} else {
isUpdate.value = false;
}
},
{ immediate: true }
);
</script>
<template>
<ele-page>
<ele-card
:body-style="{ paddingTop: '30px' }"
>
<table-info
ref="childRef"
:edit-mode="true"
v-model:loading="loading"
:user-union-info="userUnionInfo"
:brand-form="brandForm"
@editFinish="editFinish"
/>
<template #footer>
<div class="foot-btn">
<el-button @click="handleCancel">取消</el-button>
<el-button
v-if="!isUpdate"
type="primary"
:loading="loading"
@click="save(0)"
>
保存草稿
</el-button>
<el-button type="primary" :loading="loading" @click="save(1)">
提交申请
</el-button>
</div>
</template>
</ele-card>
</ele-page>
</template>
<style scoped>
.foot-btn {
padding-right: 60px;
display: flex;
justify-content: flex-end;
}
</style>
@@ -0,0 +1,209 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<div slot="header">
<span>{{form.id ? '修改申请' : '创建申请'}}</span>
</div>
<el-form ref="form" :model="form" label-width="130px">
<el-row :gutter="15">
<el-col :span="12">
<el-form-item label="工作室名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="创建年份">
<el-date-picker v-model="form.createYear" type="year" value-format="yyyy" style="width: 100%"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属工会">
<el-input v-model="form.unionName"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属机构">
<el-input v-model="form.deptName"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人">
<el-input v-model="form.leadPeople"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人年龄">
<el-input-number v-model="form.leadAge" :min="0" :max="150" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人身份">
<el-input v-model="form.leadIdentity"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人荣誉">
<el-input v-model="form.leadHonor"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工作室类型">
<el-input v-model="form.type"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属赛道">
<el-input v-model="form.track"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属领域">
<el-input v-model="form.domain"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成员人数">
<el-input-number v-model="form.memberNum" :min="1" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="工作室地址">
<el-input v-model="form.address"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="工作室介绍">
<el-input type="textarea" :rows="4" v-model="form.introduction"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input type="textarea" :rows="2" v-model="form.remark"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">工作室成员</el-divider>
<el-table :data="form.brandStudioMembers" border size="small">
<el-table-column label="姓名" min-width="120">
<template slot-scope="{row}">
<el-input v-model="row.name"></el-input>
</template>
</el-table-column>
<el-table-column label="角色" min-width="120">
<template slot-scope="{row}">
<el-input v-model="row.role"></el-input>
</template>
</el-table-column>
<el-table-column label="出生年月" min-width="120">
<template slot-scope="{row}">
<el-input v-model="row.birthdate"></el-input>
</template>
</el-table-column>
<el-table-column label="学历" min-width="120">
<template slot-scope="{row}">
<el-input v-model="row.grade"></el-input>
</template>
</el-table-column>
<el-table-column label="职称" min-width="120">
<template slot-scope="{row}">
<el-input v-model="row.professional"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width="90">
<template slot-scope="scope">
<el-button type="text" @click="removeMember(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 10px">
<el-button size="small" @click="addMember">添加成员</el-button>
</div>
<el-form-item style="margin-top: 20px">
<el-button @click="goBack">返回</el-button>
<el-button type="primary" :loading="loading" @click="save(0)">保存草稿</el-button>
<el-button type="primary" :loading="loading" @click="save(6)">提交申请</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
loading: false,
form: {
archive: 0,
status: 0,
brandStudioMembers: []
}
}
},
methods: {
getParam(name) {
return new URLSearchParams(window.location.search).get(name)
},
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
loadUserInfo() {
const user = this.$store.state.user || {}
const unit = user.unit || {}
const union = user.union || {}
this.form.unionId = union.id || unit.unionId
this.form.unionName = union.name
this.form.deptid = user.unitId || unit.id
this.form.deptName = unit.name
},
loadDetail(id) {
this.$axios.get('/platform/zhgh/brand/application/' + id).then((res) => {
const data = this.getData(res) || {}
data.brandStudioMembers = data.brandStudioMembers || []
this.form = data
})
},
addMember() {
this.form.brandStudioMembers.push({})
},
removeMember(index) {
this.form.brandStudioMembers.splice(index, 1)
},
save(status) {
this.loading = true
this.form.status = status
const request = this.form.id
? this.$axios.post('/platform/zhgh/brand/application/update', this.form)
: this.$axios.post('/platform/zhgh/brand/application', this.form)
request.then(() => {
this.$message.success('保存成功')
this.goBack()
}).finally(() => {
this.loading = false
})
},
goBack() {
window.location.href = '/platform/zhgh/brand/application/index'
}
},
created() {
const id = this.getParam('id')
if (id) {
this.loadDetail(id)
} else {
this.loadUserInfo()
this.addMember()
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,149 @@
<script setup>
import { onMounted, ref, watch } from 'vue';
import { BRAND_APPLICATION_PATH } from '@/config/setting';
import {
getBrandStudioApplicationApi,
getUserBelongUnionApi
} from '@/api/office/brand/index.js';
import { useRoute, useRouter } from 'vue-router';
import $enums from '@/utils/enums.js';
import BusinessInstanceTimeLine from '@/components/BusinessInstanceTimeLine/index.vue';
import ApplyInfo from '@/views/brand/components/apply-info.vue';
const { push } = useRouter();
const route = useRoute();
const childRef = ref();
/** 是否是修改:针对整体 */
const isUpdate = ref(false);
/** 提交状态 */
const loading = ref(false);
const handleCancel = () => {
push(BRAND_APPLICATION_PATH);
};
const save = (saveType) => {
loading.value = true;
childRef.value.submit({
saveType: saveType,
isUpdate: isUpdate.value
});
};
// 编辑结束回调
const editFinish = () => {
push(BRAND_APPLICATION_PATH);
};
const brandForm = ref({});
const queryInfoById = (id) => {
loading.value = true;
getBrandStudioApplicationApi(id)
.then(({ data }) => {
brandForm.value = data;
})
.catch((err) => {
console.log(err);
})
.finally(() => {
loading.value = false;
});
};
const unionId = ref('');
const topUnionId = ref('');
const userUnionInfo = ref({});
// 获取成员当前所在工会
const loadUserUnion = async () => {
const { data } = await getUserBelongUnionApi();
unionId.value = data.unionId;
topUnionId.value = data.topUnionId;
userUnionInfo.value = data;
};
onMounted(async () => {
console.log('mounted');
await loadUserUnion();
});
watch(
() => route.params.id,
() => {
const id = route.params.id;
if (id) {
queryInfoById(id);
isUpdate.value = true;
} else {
isUpdate.value = false;
}
},
{ immediate: true }
);
</script>
<template>
<ele-page>
<!-- :header="isUpdate ? '修改申请' : '创建申请'"-->
<ele-card :body-style="{ paddingTop: '8px' }" v-loading="loading">
<div class="container-fluid">
<el-scrollbar style="height: 730px; flex: 1;min-width: 800px">
<div style="padding: 0 40px 0 30px">
<h3 style="color: black">{{
isUpdate ? '修改申请' : '创建申请'
}}</h3>
<apply-info
ref="childRef"
:edit-mode="true"
v-model:loading="loading"
:user-union-info="userUnionInfo"
:brand-form="brandForm"
@editFinish="editFinish"
/>
<div class="foot-btn">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" :loading="loading" @click="save(0)">
保存草稿
</el-button>
<el-button type="primary" :loading="loading" @click="save(6)">
提交申请
</el-button>
</div>
</div>
</el-scrollbar>
<div class="border-left ml-10px mr-20px"></div>
<div class="container-right">
<h3 style="color: black">审批流程</h3>
<BusinessInstanceTimeLine
ref="businessInstanceTimeLine"
v-model:business-data="userUnionInfo"
:oa-key="$enums.BUSINESS_PROCESS_KEY.BRAND_OFFICE_USER_APPLY"
/>
</div>
</div>
</ele-card>
</ele-page>
</template>
<style scoped>
@import url('@/styles/workflow.scss');
.container-fluid {
display: flex;
justify-content: space-between;
}
:deep(.el-scrollbar__bar.is-vertical) {
display: none;
}
.foot-btn {
display: flex;
justify-content: flex-end;
margin: 5px 0 30px 0;
}
.container-right {
min-width: 350px;
}
</style>
@@ -0,0 +1,699 @@
<script setup>
import {StrUtil} from "@/utils/toolkit.js";
import {DeleteOutlined} from "@/components/icons/index.js";
import SurveyTreeSelect from "@/views/survey/components/survey-tree-select.vue";
import {QuestionFilled} from "@element-plus/icons-vue";
import {computed, onMounted, reactive, ref, watch} from "vue";
import {useDictData} from "@/utils/use-dict-data.js";
import {useFormData} from "@/utils/use-form-data.js";
import {ElMessageBox} from "element-plus";
import {
addBrandStudioApplicationApi,
getLeaderHonorApi,
updateBrandStudioApplicationApi
} from "@/api/office/brand/index.js";
import UnionUserSelect from "@/components/UnionUserSelect/index.vue";
import {EleMessage} from "ele-admin-plus";
import {withRequiredHeader} from "@/views/brand/js/common-brand.js";
import {useUserStore} from "@/store/modules/user.js";
const props = defineProps({
editMode: Boolean,
loading: Boolean,
userUnionInfo: Object, // 用户工会相关信息
topUnionId: String, // 最上层工会ID
brandForm: Object // 表格信息,修改时使用
})
const emit = defineEmits(['update:loading', 'editFinish'])
/** 领衔人荣誉 */
const leaderHonors = ref([]);
const userStore = useUserStore()
/** 表单实例 */
const formRef = ref(null);
/** 字典数据 */
const [
leadPeopleDict,
brandOfficeTypeDict,
userEducationDict,
brandOfficeDomainDict
] = useDictData([
'lead_people',
'brand_office_type',
'user_education',
'brand_office_domain'
]);
/** 表单数据 */
const [form, resetFields, assignFields] = useFormData({
id: '',
unionName: '', // 所属单位工会
deptName: '',
name: '', // 工作室名称
createYear: '', // 创建年度
leadPeople: '', // 工作室领衔人
leadAge: 0, // 领衔人年龄
leadIdentity: [], // 领衔人身份
leadHonor: '', // 领衔人荣誉称号
memberNum: 0, // 成员人数
type: '', // 工作室类型
track: '', // 工作室赛道
address: '', // 工作室具体位置
introduction: '', // 工作室基本情况介绍
brandStudioMembers: [] // 工作室成员 { name, birthday, grade, professional, unionName }
});
/** 表单验证规则 */
const rules = reactive({
name: [
{
required: true,
message: '请输入工作室名称',
type: 'string',
trigger: 'blur'
}
],
unionName: [
{
required: true,
message: '请输入所属单位工会',
type: 'string',
trigger: 'blur'
}
],
deptName: [
{
required: true,
message: '请输入所属单位机构',
type: 'string',
trigger: 'blur'
}
],
createYear: [
{
required: true,
message: '请选择创建年度',
type: 'string',
trigger: 'change'
}
],
leadPeople: [
{
required: true,
message: '请选择工作室领衔人',
type: 'string',
trigger: 'blur'
},
{ type: 'string', max: 20, message: '领衔人最多 20 个字符', trigger: ['blur', 'change'] }
],
leadAge: [
{
required: true,
message: '请输入领衔人年龄',
type: 'number',
trigger: 'blur'
},
{
type: 'number',
min: 0,
max: 150,
message: '年龄必须在0-150岁之间',
trigger: 'blur'
}
],
leadIdentity: [
{
required: true,
message: '请选择领衔人身份',
type: 'array',
trigger: 'change'
}
],
leadHonor: [
{
required: true,
message: '请输入领衔人荣誉称号',
type: 'string',
trigger: 'change'
}
],
memberNum: [
{
required: true,
message: '请输入成员人数',
type: 'number',
trigger: 'blur'
}
],
type: [
{
required: true,
message: '请选择工作室类型',
type: 'string',
trigger: 'change'
}
],
track: [
{
required: true,
message: '请输入工作室赛道',
type: 'string',
trigger: 'blur'
}
],
address: [
{
required: true,
message: '请输入工作室具体位置',
type: 'string',
trigger: 'blur'
}
],
introduction: [
{
required: true,
message: '请填写工作室基本情况介绍',
type: 'string',
trigger: 'blur'
}
],
brandStudioMembers: [
{
required: true,
validator: (rule, value, callback) => {
// 1. 必须是数组且长度 >= 3
if (!Array.isArray(value) || value.length < 1) {
ElMessageBox.alert(
`请至少选择1位工作室成员!`,
`系统提示`, {
confirmButtonText: '确定',
type: 'warning',
});
return callback(new Error('请至少选择1位工作室成员'));
}
// 2. 检查每个成员的必填字段
const requiredFields = columns.value
.filter((col) => col.notRequired !== true)
.map((col) => col.prop); // ['name', 'birthdate', ...]
for (let i = 0; i < value.length; i++) {
const item = value[i];
for (const field of requiredFields) {
const val = item?.[field];
if (StrUtil.isEmpty(val)) {
const label = fieldLabelMap[field] || field; // fallback to field name
ElMessageBox.alert(
`${item.name}】成员的【${label}】不能为空!`,
`系统提示`, {
confirmButtonText: '确定',
type: 'warning',
});
return callback(
new Error(`${item.name} 的“${label}”不能为空`)
);
}
}
}
callback(); // 校验通过
},
trigger: 'change'
}
]
});
/** 二级弹窗是否打开:选择用户 */
const selectUserVisible = ref(false);
/** beforeConfirm 为确定按钮点击钩子, 可以 return false 阻止确定 */
const beforeConfirm = (data) => {
if (!data?.length) {
EleMessage.error('请至少选择一个用户');
return false;
}
};
/** 表格列配置 */
const columns = computed(() => [
withRequiredHeader({ prop: 'name', label: '姓名', align: 'center', minWidth: 80, slot: 'name' }),
withRequiredHeader({ prop: 'birthdate', label: '出生年月', align: 'center', minWidth: 120, slot: 'birthdate', required: true }),
withRequiredHeader({ prop: 'grade', label: '学历', align: 'center', minWidth: 80, slot: 'grade', required: true }),
withRequiredHeader({ prop: 'professional', label: '职称', align: 'center', minWidth: 100, slot: 'professional', required: true }),
withRequiredHeader({ prop: 'deptName', label: '所在部门', align: 'center', minWidth: 100, slot: 'deptName' }),
withRequiredHeader({ columnKey: 'action', label: '操作', width: 80, align: 'center', slot: 'action', notRequired: true })
]);
// 自动生成映射:{ name: '姓名', birthdate: '出生年月', ... }
const fieldLabelMap = {};
columns.value.forEach((col) => {
fieldLabelMap[col.prop] = col.label;
});
const leaderChange = (emplid) => {
console.log('emplid: ' + emplid);
const matchedItem = leaderHonors.value.find(
(item) => item.emplid === emplid
);
if (matchedItem) {
form.leadHonor = matchedItem.honorNames?.join(',') || '';
form.leadAge = matchedItem.age || 0;
form.leadPeople = matchedItem.userName || '';
} else {
form.leadHonor = '';
form.leadAge = 0;
}
};
const unionAssign = (item) => {
if (!item) {
form.unionName = '';
return;
}
form.unionName = item.name;
}
const showSelectUser = () => {
selectUserVisible.value = true;
};
// 可编辑的副本(用于 v-model)
const editableData = ref([]);
const removeMember = (row) => {
const index = editableData.value.findIndex(
(item) => item.userId === row.userId
);
if (index !== -1) {
userIds.value.splice(index, 1);
editableData.value.splice(index, 1);
form.memberNum--;
}
};
/** 表格实例 */
const tableRef = ref(null);
// 可选择的成员
const userIds = ref([]);
/** 选择成员 */
// user-select 组件回调
const handleSelect = (data) => {
// orgEditableData 原来数据
const orgEditableData = editableData.value;
// 查找已经存在的用户
const existingIds = new Set(
orgEditableData.map((item) => item.userId || item.id)
);
// 过滤掉已经选择的用户,之前有的就不做处理
const selectData = data
.filter((item) => !existingIds.has(item.id))
.map((item) => ({
...item,
userId: item.id,
oldName: item.name,
oldBirthdate: item.birthdate
}));
editableData.value = [...orgEditableData, ...selectData];
form.memberNum = editableData.value.length;
// 选完情况组件中的用户
userIds.value = [];
};
// 获取成员当前所在工会和所有工会
const assignUserUnion = (userUnionInfo) => {
form.unionId = userUnionInfo.unionId;
form.unionName = userUnionInfo.unionName;
form.deptid = userUnionInfo.deptId;
form.deptName = userUnionInfo.deptName;
form.createYear = new Date().getFullYear().toString();
}
watch(() => props.userUnionInfo,
(newVal, oldVal) => {
if (StrUtil.isBlank(newVal?.unionId)) {
return;
}
assignUserUnion(props.userUnionInfo);
}, {immediate: true}
)
watch(
() => props.brandForm,
(newVal, oldVal) => {
if (StrUtil.isBlank(newVal?.id)) {
return
}
const data = {
...newVal,
leadIdentity: JSON.parse(newVal?.leadIdentity)
}
assignFields(data)
editableData.value =
data.brandStudioMembers.map((item) => {
return {
...item,
oldName: item.name,
oldBirthdate: item.birthdate
};
}) || [];
}, {immediate: true}
)
const userUnionId = ref('')
onMounted(async () => {
console.log('mounted');
// 调用接口获取所有工会列表并转为tree
userUnionId.value = userStore.userUnionIdByRole || userStore.userUnionId || '0';
console.log("=======", userUnionId.value)
const res = await getLeaderHonorApi();
leaderHonors.value = res.data.map((item, index) => {
return {
id: index,
emplid: item.emplid,
userName: item.userName,
age: item.age,
honorNames: item.honorNames
};
});
});
// 提交数据/保存草稿
const submit = (item) => {
form.status = item.saveType;
form.brandStudioMembers = editableData.value || [];
const params = { ...form };
params.leadIdentity = JSON.stringify(form.leadIdentity) || {};
params.domain = params.track;
formRef.value?.validate?.((valid) => {
if (!valid) {
emit('update:loading', false)
return;
}
const saveOrUpdate = item.isUpdate
? updateBrandStudioApplicationApi
: addBrandStudioApplicationApi;
saveOrUpdate(params)
.then(() => {
EleMessage.success({ message: '保存成功', plain: true });
emit('editFinish')
})
.catch((err) => {
EleMessage.error({ message: '服务错误', plain: true });
console.warn(err);
})
.finally(() => {
emit('update:loading', false)
});
})
}
defineExpose({
submit
})
</script>
<template>
<div v-loading="loading">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="110px"
@submit.prevent=""
:disabled="!editMode"
>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="所属单位工会" prop="unionName" required>
<survey-tree-select class="ele-fluid" v-model="form.unionId" :union-id="userUnionId" placeholder="请选择所属工会" @unionAssign="unionAssign" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属单位机构" prop="deptName" required>
<el-input
v-model="form.deptName"
placeholder="请输入所属单位机构"
:disabled="true"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室名称" prop="name" required>
<el-input
v-model="form.name"
placeholder="请输入工作室名称"
maxlength="30"
show-word-limit
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="创建年度" required prop="createYear">
<el-date-picker
clearable
v-model="form.createYear"
type="year"
value-format="YYYY"
placeholder="请选择年度"
:disabled="true"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室领衔人" prop="leadPeople" required>
<el-select
v-model="form.leadPeople"
filterable
allow-create
default-first-option
placeholder="请选择工作室领衔人"
value-key="id"
@change="leaderChange"
clearable
>
<el-option
v-for="item in leaderHonors"
:key="item.emplid"
:value="item.emplid"
:label="`${item.emplid} - ${item.userName}`"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人年龄" required prop="leadAge">
<el-input-number
placeholder="请输入领衔人年龄"
v-model="form.leadAge"
:min="0"
:max="150"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="领衔人身份" prop="leadIdentity">
<el-select
placeholder="请选择领衔人身份"
v-model="form.leadIdentity"
multiple
value-key="id"
clearable
>
<el-option
v-for="item in leadPeopleDict"
:key="item.id"
:value="{
id: item.id,
dictLabel: item.dictLabel,
dictTypeId: item.dictTypeId
}"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="荣誉称号" required prop="leadHonor">
<el-input
placeholder="请输入领衔人荣誉称号"
v-model="form.leadHonor"
maxlength="40"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室类型" prop="type">
<el-select
placeholder="请选择工作室类型"
v-model="form.type"
value-key="id"
clearable
>
<el-option
v-for="item in brandOfficeTypeDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属赛道" prop="track" required>
<el-select
placeholder="请选择工作室赛道"
v-model="form.track"
value-key="id"
clearable
>
<el-option
v-for="item in brandOfficeDomainDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="具体位置" prop="address" required>
<el-input
v-model="form.address"
placeholder="请输入工作室具体位置"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="基本情况介绍" required prop="introduction">
<el-input
:rows="8"
type="textarea"
v-model="form.introduction"
placeholder="请输入基本情况介绍"
clearable
maxlength="1500"
show-word-limit
/>
</el-form-item>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item
label="工作室成员"
required
prop="brandStudioMembers"
>
<span>{{ form.memberNum }}</span>
<el-tooltip
content="团队成员数量,根据选择成员人数自动变化"
placement="top"
>
<el-icon
style="cursor: help; margin-left: 4px; color: #909399"
>
<QuestionFilled />
</el-icon>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12" style="text-align: right">
<el-button type="primary" @click="showSelectUser">
选择工作室成员
</el-button>
</el-col>
</el-row>
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="editableData"
:show-overflow-tooltip="true"
highlight-current-row
:export-config="{ fileName: '用户数据' }"
:style="{ paddingBottom: '16px' }"
cache-key="systemUserTable"
:tools="false"
>
<template #name="{ row }">
<span v-if="StrUtil.isEmpty(row.oldName)">
<el-input v-model="row.name" />
</span>
<span v-else>{{ row.name }}</span>
</template>
<template #birthdate="{ row }">
<el-date-picker
style="width: 160px; padding: 0 20px"
v-model="row.birthdate"
type="month"
placeholder="选择年月"
format="YYYY-MM"
value-format="YYYY-MM"
size="large"
/>
</template>
<template #grade="{ row }">
<span>
<el-select
placeholder="请选择学历类型"
v-model="row.grade"
value-key="id"
clearable
>
<el-option
v-for="item in userEducationDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</span>
</template>
<template #professional="{ row }">
<span class="ipt-text">
<el-input v-model="row.professional" maxlength="30" show-word-limit />
</span>
</template>
<template #deptName="{ row }">
<span v-if="StrUtil.isEmpty(row.deptName)">
<el-input v-model="row.deptName" />
</span>
<span v-else>{{ row.deptName }}</span>
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:remove'"
type="danger"
underline="never"
@click="removeMember(row)"
:icon="DeleteOutlined"
>
删除
</el-link>
</template>
</ele-pro-table>
</el-form>
<union-user-select
clearable
multiple
view-type="picker"
v-model:visible="selectUserVisible"
v-model="userIds"
placeholder="请选择用户"
queryType="union"
:union-id="topUnionId"
@select="handleSelect"
:before-confirm="beforeConfirm"
/>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,528 @@
<!-- 审批弹窗 -->
<template>
<ele-modal
:form="true"
:destroy-on-close="true"
:width="1200"
v-model="visible"
title="申请详情"
>
<el-row :gutter="24" style="padding-bottom: 16px">
<el-col :span="24">
<ele-timeline
:data="flowItems"
style="margin-top: 28px; font-size: 14px !important; width: 100%"
placement="left"
>
<template #itemDescription="{ item }">
<div class="flow-text">{{ item.auditor }}</div>
<div v-if="item.time" class="flow-text">
{{ item.key === 1 ? '申请时间' : '审批时间' }}{{ item.time }}
</div>
<el-tooltip class="item" effect="dark" :content="item.remark" placement="top">
<div
v-if="item.remark && getStatusDesc(item.status) === 'PASSED'"
class="flow-text"
>审批意见{{ item.remark }}
</div>
<div
v-if="item.remark && getStatusDesc(item.status) === 'REJECT'"
class="flow-text"
>驳回原因{{ item.remark }}
</div>
<div
v-if="item.remark && getStatusDesc(item.status) === 'REPEAL'"
class="flow-text"
>
撤销原因{{ item.remark }}
</div>
</el-tooltip>
</template>
</ele-timeline>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<info-audit :data="current" disabled/>
<!-- <el-card header="申请信息" shadow="never">-->
<!-- <info-audit :data="current" disabled></info-audit>-->
<!-- </el-card>-->
</el-col>
</el-row>
<template #footer>
<div
v-if="
getStatusNameByCode(props.data.status) === 'IN_REVIEW' &&
props.isAudit
"
>
<el-button :loading="loading" @click="handleRepeal"> 撤销 </el-button>
<el-button :loading="loading" @click="handleRefuse">驳回</el-button>
<el-button type="primary" :loading="loading" @click="handlePass">
通过
</el-button>
</div>
</template>
</ele-modal>
<el-dialog
v-model="passDialogVisible"
title="审批意见"
width="400px"
:before-close="handlePassDialogClose"
>
<el-form :model="passForm" :rules="passRules" ref="passFormRef">
<el-form-item label="审核意见" prop="reason">
<el-input
v-model="passForm.reason"
type="textarea"
:rows="4"
placeholder="请输入审核意见(必填)"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="passDialogVisible = false">取消</el-button>
<el-button type="primary" @click="onAudit" :loading="loading"
>确认</el-button
>
</template>
</el-dialog>
<!-- 新增:拒绝原因弹窗 -->
<el-dialog
v-model="refuseDialogVisible"
title="驳回原因"
width="400px"
:before-close="handleRefuseDialogClose"
>
<el-form :model="refuseForm" :rules="refuseRules" ref="refuseFormRef">
<el-form-item label="驳回原因" prop="reason">
<el-input
v-model="refuseForm.reason"
type="textarea"
:rows="4"
placeholder="请输入驳回原因(必填)"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="refuseDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmRefuse" :loading="loading"
>确认</el-button
>
</template>
</el-dialog>
<!-- 新增:撤销原因弹窗 -->
<el-dialog
v-model="repealDialogVisible"
title="撤销原因"
width="400px"
:before-close="handleRepealDialogClose"
>
<el-form :model="repealForm" :rules="repealRules" ref="repealFormRef">
<el-form-item label="撤销原因" prop="reason">
<el-input
v-model="repealForm.reason"
type="textarea"
:rows="4"
placeholder="请输入撤销原因(必填)"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="repealDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmRepeal" :loading="loading"
>确认</el-button
>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch } from 'vue';
import { EleMessage } from 'ele-admin-plus';
import { getBrandStudioApplicationApi } from '@/api/office/brand/index.js';
import {
auditApplicationApi,
getBrandApplyFlowListApi
} from '@/api/office/application/index.js';
import InfoAudit from '@/views/brand/components/info-audit.vue';
import { getStatusDesc, getStatusNameByCode } from '@/utils/toolkit.js';
const props = defineProps({
/** 修改回显的数据 */
data: Object,
/** 查看/审批 */
isAudit: Boolean
});
const emit = defineEmits(['done']);
/** 弹窗是否打开 */
const visible = defineModel({ type: Boolean });
/** 提交状态 */
const loading = ref(false);
/** 工作流数组 */
const flowItems = ref([]);
// ========== 新增:拒绝功能相关代码 ==========
// 拒绝弹窗显示状态
const refuseDialogVisible = ref(false);
// 拒绝表单数据
const refuseForm = ref({
reason: '' // 拒绝原因
});
// 拒绝表单验证规则
const refuseRules = reactive({
reason: [
{
required: true,
message: '请输入拒绝原因',
trigger: 'blur'
},
{
min: 2,
max: 500,
message: '拒绝原因长度在 2 到 500 个字符',
trigger: 'blur'
}
]
});
// 拒绝表单引用
const refuseFormRef = ref();
// 打开驳回
const handleRefuse = () => {
// 清空之前的驳回原因
refuseForm.value.reason = '';
// 打开弹窗
refuseDialogVisible.value = true;
};
// 关闭驳回弹窗时的处理
const handleRefuseDialogClose = () => {
refuseDialogVisible.value = false;
};
// 确认驳回
const confirmRefuse = () => {
// 验证表单
refuseFormRef.value.validate((valid) => {
if (!valid) {
return;
}
loading.value = true;
// 调用驳回接口
const payload = {};
payload.applicationId = props.data.id;
payload.status = 2;
payload.flowId = props.data.flowId;
payload.remark = refuseForm.value.reason;
auditApplicationApi(payload)
.then(() => {
EleMessage.success({ message: '驳回成功', plain: true });
refuseDialogVisible.value = false;
handleCancel();
emit('done');
})
.catch(() => {
EleMessage.error({ message: '拒绝失败', plain: true });
})
.finally(() => {
loading.value = false;
});
});
};
const current = ref({});
/** 关闭弹窗 */
const handleCancel = () => {
visible.value = false;
};
//获取基本信息详细
const getBrandApplicationDetail = () => {
getBrandStudioApplicationApi(props.data.id)
.then(({ data }) => {
loading.value = false;
current.value = data;
})
.finally(() => {
loading.value = false;
});
};
// ========== 新增:通过功能相关代码 ==========
const passFormRef = ref()
const passDialogVisible = ref(false);
// 撤销表单数据
const passForm = ref({
reason: '' // 通过原因
});
// 关闭通过弹窗时的处理
const handlePassDialogClose = () => {
passDialogVisible.value = false;
};
//审核通过
const onAudit = () => {
passFormRef.value.validate((valid) => {
if (!valid) {
return;
}
const payload = {};
payload.flowId = props.data.flowId;
payload.applicationId = props.data.id;
payload.status = 5;
payload.remark = passForm.value.reason;
const loadingInstance = EleMessage.loading({
message: '请求中...',
plain: true,
duration: 0 // 手动控制关闭,不自动消失
});
auditApplicationApi(payload)
.then(() => {
EleMessage.success({
message: '审核通过',
plain: true
});
passDialogVisible.value = false;
handleCancel();
emit('done');
})
.finally(() => {
loadingInstance.close();
});
})
// ElMessageBox.confirm(`是否通过审核?`, '系统提示', {
// type: 'warning',
// draggable: true
// })
// .then(() => {
// const payload = {};
// payload.flowId = props.data.flowId;
// payload.applicationId = props.data.id;
// payload.status = 5;
// const loadingInstance = EleMessage.loading({
// message: '请求中...',
// plain: true,
// duration: 0 // 手动控制关闭,不自动消失
// });
// auditApplicationApi(payload)
// .then(({ message, data }) => {
// EleMessage.success({
// message: '审核通过',
// plain: true
// });
// handleCancel();
// emit('done');
// })
// .finally(() => {
// loadingInstance.close();
// });
// })
// .catch(() => {});
};
// 打开撤销
const handlePass = () => {
// 清空之前的驳回原因
passForm.value.reason = '';
// 打开弹窗
passDialogVisible.value = true;
};
// 撤销表单验证规则
const passRules = reactive({
reason: [
{
required: true,
message: '请输入通过原因',
trigger: 'blur'
},
{
min: 2,
max: 500,
message: '通过原因长度在 2 到 500 个字符',
trigger: 'blur'
}
]
});
// ========== 新增:撤销功能相关代码 ==========
// 撤销弹窗显示状态
const repealDialogVisible = ref(false);
// 撤销表单数据
const repealForm = ref({
reason: '' // 撤销原因
});
// 撤销表单验证规则
const repealRules = reactive({
reason: [
{
required: true,
message: '请输入撤销原因',
trigger: 'blur'
},
{
min: 2,
max: 500,
message: '撤销原因长度在 2 到 500 个字符',
trigger: 'blur'
}
]
});
// 撤销表单引用
const repealFormRef = ref(null);
// 打开撤销
const handleRepeal = () => {
// 清空之前的驳回原因
repealForm.value.reason = '';
// 打开弹窗
repealDialogVisible.value = true;
};
// 关闭撤销弹窗时的处理
const handleRepealDialogClose = () => {
repealDialogVisible.value = false;
};
// 确认撤销
const confirmRepeal = () => {
// 验证表单
repealFormRef.value.validate((valid) => {
if (!valid) {
return;
}
loading.value = true;
// 调用接口
const payload = {};
payload.status = 3;
payload.flowId = props.data.flowId;
payload.applicationId = props.data.id;
payload.remark = repealForm.value.reason;
auditApplicationApi(payload)
.then(() => {
EleMessage.success({ message: '撤销成功', plain: true });
repealDialogVisible.value = false;
handleCancel();
emit('done');
})
.catch(() => {})
.finally(() => {
loading.value = false;
});
});
};
//流程实例
const getApplyFlowList = async () => {
const requestParams = {
applicationId: props.data.id // 关键Query参数:申请单ID
};
const { data } = await getBrandApplyFlowListApi(requestParams);
if (data && data.length > 0) {
const flowList = data.map((item, index) => {
let type = '';
// 定义不同状态的节点类型
if (getStatusDesc(item.status) === 'PASSED') {
type = 'primary';
} else if (getStatusDesc(item.status) === 'REJECT') {
type = 'danger';
}
return {
key: index + 1,
type: type,
title: item.unionName,
time:
getStatusDesc(item.status) !== 'WAIT_AUDIT' &&
getStatusDesc(item.status) !== 'TO_BE_AUDIT'
? item.updateTime
: '',
remark: item.remark,
auditor: item.auditor,
sortNo: item.sortNo,
status: item.status
};
});
flowItems.value = flowList.sort((a, b) => a.sortNo - b.sortNo);
}
};
/** 监听弹窗打开 */
watch(visible, () => {
if (visible.value && props.data) {
getBrandApplicationDetail();
getApplyFlowList();
}
});
</script>
<style scoped>
.el-steps--vertical {
height: 20% !important;
}
.auditor-time-row {
display: flex;
width: 100%;
justify-content: space-between;
padding-top: 8px;
}
.main-title {
font-weight: bold;
}
.auditor-date-time {
color: #9c9c9c;
}
.ele-modal-body.is-form {
padding-bottom: 20px !important;
}
:deep(.ele-time-line-list) {
margin-top: 0 !important;
}
:deep(.ele-time-line-item-title) {
font-size: 14px !important;
}
:deep(.ele-time-line-item-body) {
padding-bottom: 20px !important;
}
:deep(.el-timeline-item .is-placeholder) {
display: none !important; /* 直接隐藏占位 DOM */
height: 0 !important; /* 兜底:清空高度 */
margin: 0 !important; /* 清空边距 */
}
:deep(.is-placeholder) {
display: none;
}
</style>
<style>
.flow-text {
display: -webkit-box; /* For flexbox-like behavior */
-webkit-box-orient: vertical; /* Allow vertical box layout */
-webkit-line-clamp: 3; /* Limit text to 3 lines */
overflow: hidden; /* Hide overflow */
text-overflow: ellipsis; /* Show ellipsis for overflow */
white-space: normal; /* Allow text to wrap */
font-size: 11px;
width: 100%;
justify-content: center;
padding-top: 3px;
}
</style>
@@ -0,0 +1,50 @@
<!-- 审批弹窗 -->
<template>
<div>
<el-row :gutter="20">
<el-col :span="24">
<info-audio :data="current" :edit-mode="true"/>
<!-- <table-info-->
<!-- v-model:loading="loading"-->
<!-- :edit-mode="false"-->
<!-- :brand-form="current"-->
<!-- />-->
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import InfoAudio from '@/views/brand/components/info-audit.vue';
// import TableInfo from "@/views/brand/components/table-info.vue";
import { useRoute } from 'vue-router';
import {getBrandStudioApplicationApi} from "@/api/office/brand/index.js";
const loading = ref(false)
const route = useRoute();
const current = ref({});
//获取基本信息
const getBrandApplicationDetail = async () => {
// console.log("getBrandApplicationDetail", route.query)
loading.value = true
getBrandStudioApplicationApi(route.query.businessId)
.then(({ data }) => {
current.value = data;
})
.catch(err => console.log(err))
.finally(() => {
loading.value = false
});
};
onMounted(() => {
getBrandApplicationDetail();
});
</script>
<style scoped>
</style>
@@ -0,0 +1,242 @@
<!-- 弹窗先进工作者 -->
<template>
<el-form ref="formRef" :model="form" label-width="120" @submit.prevent="" :disabled="editMode">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="所属单位工会" prop="unionName">
<el-input
v-model="form.unionName"
placeholder="请输入所属单位工会"
:disabled="true"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属单位机构" prop="deptName">
<el-input
v-model="form.deptName"
placeholder="请输入所属单位机构"
:disabled="true"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="工作室名称" prop="name">
<el-input v-model="form.name" placeholder="请输入工作室名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="创建年份" prop="createYear">
<el-input v-model="form.createYear" placeholder="请选择年度"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="工作室领衔人" prop="leadPeople">
<el-input
v-model="form.leadPeople"
placeholder="请输入工作室领衔人"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人年龄" prop="leadAge">
<el-input-number
placeholder="请输入成员人数"
v-model="form.leadAge"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="领衔人身份" prop="leadIdentity">
<el-input
placeholder="请选择领衔人身份"
v-model="form.leadIdentity"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="荣誉称号" prop="leadHonor">
<el-input
placeholder="请输入领衔人荣誉称号"
v-model="form.leadHonor"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="工作室类型" prop="type">
<el-select
placeholder="请选择工作室类型"
v-model="form.type"
value-key="id"
>
<el-option
v-for="(item, index) in form.type"
:key="index"
:value="item"
:label="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属赛道" prop="track">
<el-input v-model="form.track" placeholder="请输入所属赛道" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="具体位置" prop="address">
<el-input v-model="form.address" placeholder="请输入工作室具体位置" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成员人数" prop="memberNum">
<el-input v-model="form.memberNum" placeholder="请输入工作室成员人数" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="基本情况介绍" prop="introduction">
<span style="color: #999999; font-size: 12px"
>(说明:事迹材料客观翔实重点突出不超过1500字)</span
>
<el-input
:rows="4"
type="textarea"
disabled
v-model="form.introduction"
/>
</el-form-item>
<!-- 表格 -->
<ele-pro-table
row-key="id"
:columns="columns"
:datasource="tableData"
:show-overflow-tooltip="true"
highlight-current-row
:export-config="{ fileName: '用户数据' }"
:style="{ paddingBottom: '16px' }"
cache-key="systemUserTable"
:tools="false"
/>
</el-form>
</template>
<script setup>
import { ref, watch, computed } from 'vue';
import { useFormData } from '@/utils/use-form-data';
const props = defineProps({
/** 修改回显的数据 */
data: Object,
editMode: Boolean,
});
defineEmits(['done', 'back']);
/** 弹窗是否打开 */
defineModel({ type: Boolean });
/** 表单实例 */
const formRef = ref();
/** 表单数据 */
const [form, resetFields, assignFields] = useFormData({
id: '',
unionName: '', // 所属单位工会
deptName: '', // 所属单位工会
name: '', // 工作室名称
createYear: '', // 创建年份
leadPeople: '', // 工作室领衔人
leadAge: 25, // 领衔人年龄
leadIdentity: '', // 领衔人身份
leadHonor: '',
memberNum: 0, // 成员人数
type: '', // 工作室类型
track: '', // 工作室赛道
address: '', // 工作室具体位置
introduction: '', // 工作室基本情况介绍
unionUsers: [], // 工作室成员 { name, birthday, grade, professional, unionName }
remark: ''
});
/** 表格列配置 */
const columns = computed(() => {
return [
{
prop: 'name',
label: '姓名',
align: 'center',
minWidth: 80,
slot: 'name'
},
{
prop: 'birthdate',
label: '出生年月',
align: 'center',
minWidth: 120,
slot: 'birthdate'
},
{
prop: 'grade',
label: '学历',
align: 'center',
minWidth: 80,
slot: 'grade'
},
{
prop: 'professional',
label: '职称',
align: 'center',
minWidth: 100,
slot: 'professional'
},
{
prop: 'deptName',
label: '所在部门',
align: 'center',
minWidth: 100,
slot: 'deptName'
}
];
});
const tableData = ref([]);
/** 监听弹窗打开 */
watch(
() => props.data,
(newData) => {
if (newData && Object.keys(newData).length) {
// 深拷贝 newData,避免直接操作 props 原数据
const dataCopy = JSON.parse(JSON.stringify(newData));
const leadIdentity = JSON.parse(dataCopy['leadIdentity']);
tableData.value = newData.brandStudioMembers || [];
// 处理需要转换的字段
const formData = {
...dataCopy,
id: '', // 强制置空 id
leadIdentity: leadIdentity.map((item) => item.dictLabel).join(',')
};
// 用 assignFields 将处理后的数据赋值给 form(而非修改 props)
assignFields(formData);
} else {
// 数据为空时重置表单
resetFields();
}
},
{ immediate: true, deep: true }
);
</script>
<style>
</style>
@@ -0,0 +1,68 @@
<!-- 搜索表单 -->
<template>
<ele-card :body-style="{ paddingBottom: '2px' }">
<el-form label-width="72px" @keyup.enter="search" @submit.prevent="">
<el-row :gutter="8">
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="工作室名称" label-width="100px">
<el-input
clearable
v-model.trim="form.name"
placeholder="请输入工作室名称"
/>
</el-form-item>
</el-col>
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="创立年度">
<el-date-picker
clearable
v-model="form.createYear"
type="year"
value-format="YYYY"
placeholder="请选择工作室创立年度"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="工作室领衔人" label-width="100px">
<el-input
clearable
v-model.trim="form.leadPeople"
placeholder="请输入工作室领衔人"
/>
</el-form-item>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xs="24">
<el-form-item label-width="20px">
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</ele-card>
</template>
<script setup>
import { useFormData } from '@/utils/use-form-data.js';
const emit = defineEmits(['search']);
/** 表单数据 */
const [form, resetFields] = useFormData({
name: '',
leadPeople: '',
createYear: new Date().getFullYear().toString()
});
/** 搜索 */
const search = () => {
emit('search', { ...form, params: {} });
};
/** 重置 */
const reset = () => {
resetFields();
search();
};
</script>
@@ -0,0 +1,103 @@
<script setup>
import UnionUserSelect from "@/components/UnionUserSelect/index.vue";
import {EleMessage} from "ele-admin-plus";
import {ref, watch} from "vue";
import {StrUtil} from "@/utils/toolkit.js";
const props = defineProps({
unionMembers: {
type: Array,
default: () => []
},
topUnionId: {
type: String
}
})
const emit = defineEmits(['update:unionMembers'])
const selectUserVisible = ref(false)
// 可编辑的副本(用于 v-model)
const editableData = ref([]);
watch(
() => props.unionMembers,
(val) => {
editableData.value = val ? [...val] : []
},
{ immediate: true, deep: true }
)
/** 打开弹窗 */
const showSelectUser = () => {
selectUserVisible.value = true;
}
// 可选择的成员
const userIds = ref([]);
/** 选择成员 */
// user-select 组件回调
const handleSelect = (data) => {
// orgEditableData 原来数据
const orgEditableData = editableData.value.filter(item => !StrUtil.isBlank(item.name));
// 查找已经存在的用户
const existingIds = new Set(
orgEditableData.map((item) => item.userId || item.id)
);
// 过滤掉已经选择的用户,之前有的就不做处理
const selectData = data
.filter((item) => !existingIds.has(item.id))
.map((item) => ({
...item,
userId: item.id,
oldName: item.name,
oldBirthdate: item.birthdate
}));
editableData.value = [...orgEditableData, ...selectData];
// 选完情况组件中的用户
userIds.value = [];
emit('update:unionMembers', editableData.value)
};
/** beforeConfirm 为确定按钮点击钩子, 可以 return false 阻止确定 */
const beforeConfirm = (data) => {
if (!data?.length) {
EleMessage.error('请至少选择一个用户');
return false;
}
return true
};
</script>
<template>
<div>
<el-button
type="primary"
size="default"
:plain="true"
:text="true"
:bg="true"
@click="showSelectUser"
>
选择工作室成员
</el-button>
<union-user-select
clearable
multiple
view-type="picker"
v-model:visible="selectUserVisible"
v-model="userIds"
placeholder="请选择用户"
:query-type="'union'"
:union-id="topUnionId"
@select="handleSelect"
:before-confirm="beforeConfirm"
/>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,823 @@
<template>
<div style="padding: 0 100px">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="right"
:label-width="80"
:inline-message="true"
@submit.prevent=""
:disabled="!editMode"
v-loading="loading"
>
<ele-text
size="xl"
:strong="true"
:style="{ textAlign: 'center', marginBottom: '12px' }"
>
上海浦东发展银行个人品牌工作室推荐表
</ele-text>
<div style="padding: 10px 0">
<el-form-item label="所属单位工会" prop="unionId" label-position="left" label-width="110px">
<survey-tree-select class="ele-fluid" v-model="form.unionId" :union-id="userUnionId" placeholder="请选择所属工会" @unionAssign="unionAssign" />
</el-form-item>
</div>
<ele-table
:border="true"
:has-header="false"
:style="{
'--ele-table-border-color': 'var(--el-text-color-secondary)',
tableLayout: 'fixed',
marginBottom: '28px'
}"
>
<tr>
<td :style="{ width: '140px' }" class="label-center">
<ele-text tag="span" class="label-required">
工作室名称
</ele-text>
</td>
<td :style="{ minWidth: '140px' }" :colspan="3">
<el-form-item
label=""
prop="name"
label-width="0px"
label-position="left"
:style="{ marginBottom: '0px' }"
class="pro-form-error-popper"
>
<el-input
:clearable="true"
type="text"
placeholder=""
:maxlength="30"
:show-word-limit="true"
ref="nameRef"
v-model="form.name"
/>
</el-form-item>
</td>
<td :style="{ width: '120px' }" class="label-center">
<ele-text tag="span" class="label-required">
创建年度
</ele-text>
</td>
<td :colspan="2">
<el-form-item label="" prop="createYear" label-width="0px">
<el-date-picker
class="ele-fluid"
value-format="YYYY"
placeholder="请选择创建年度"
:clearable="false"
:editable="true"
type="year"
format="YYYY"
v-model="form.createYear"
/>
</el-form-item>
</td>
</tr>
<tr>
<td class="label-center">
<ele-text class="label-required" tag="span"> 工作室领衔人 </ele-text>
</td>
<td :colspan="3">
<el-form-item prop="leadPeople" label-width="0px" label-position="left">
<el-select
v-model="form.leadPeople"
filterable
allow-create
default-first-option
placeholder="请选择工作室领衔人"
value-key="id"
class="ele-fluid"
@change="leaderChange"
clearable
>
<el-option
v-for="item in leaderHonors"
:key="item.emplid"
:value="item.emplid"
:label="`${item.emplid} - ${item.userName}`"
/>
</el-select>
</el-form-item>
</td>
<td class="label-center">
<ele-text class="label-required" tag="span"> 年龄 </ele-text>
</td>
<td :colspan="2">
<el-form-item label="" prop="leadAge" label-width="0px" label-position="left">
<el-input-number
class="ele-fluid"
controls-position="right"
placeholder="请输入年龄"
:min="0"
:max="150"
:controls="false"
v-model="form.leadAge"
/>
</el-form-item>
</td>
</tr>
<tr>
<td class="label-center">
<ele-text class="label-required" tag="span"> 领衔人身份 </ele-text>
</td>
<td :colspan="3">
<el-form-item label="" prop="leadIdentity" label-width="0px" label-position="left">
<el-checkbox-group
:style="{ gap: '10px', display: 'flex', 'flex-wrap': 'wrap' }"
class=""
@change="(value) => {}"
:disabled="false"
:max="2"
v-model="form.leadIdentity"
>
<el-checkbox
v-for="item in leadPeopleDict"
:key="item.id"
:label="item.dictLabel"
:value="{
id: item.id,
dictLabel: item.dictLabel,
dictTypeId: item.dictTypeId
}"
/>
</el-checkbox-group>
</el-form-item>
</td>
<td class="label-center">
<ele-text class="label-required" tag="span"> 成员人数 </ele-text>
</td>
<td :colspan="2">
<el-form-item prop="memberNum" label-width="0px" label-position="left">
<el-input-number
class="ele-fluid"
controls-position="right"
placeholder="请输入成员人数"
:min="0"
:max="99999"
:controls="false"
v-model="form.memberNum"
/>
</el-form-item>
</td>
</tr>
<tr>
<td class="label-center">
<ele-text class="label-required" tag="span"> 工作室类型 </ele-text>
</td>
<td :colspan="3">
<el-form-item prop="type" label-width="0px" label-position="left">
<el-radio-group
class="radio-as-checkbox"
:style="{
display: 'flex',
gap: '10px',
'flex-wrap': 'wrap'
}"
v-model="form.type"
>
<el-radio
v-for="item in brandOfficeTypeDict"
:key="item.id"
:value="item.dictLabel"
>
<span>{{ item.dictLabel }}</span>
<el-input
v-if="item.dictLabel === '其他' && form.type === '其他'"
v-model="form.type"
placeholder="请输入工作室类型"
style="width:150px;margin-left:20px"
@click.stop
/>
</el-radio>
</el-radio-group>
</el-form-item>
</td>
<td class="label-center">
<ele-text class="label-required" tag="span"> 所属赛道 </ele-text>
</td>
<td :colspan="2">
<el-form-item label="" prop="track" label-width="0px" label-position="left">
<el-select
class="ele-fluid"
:clearable="false"
placeholder="请选择所属赛道"
v-model="form.track"
>
<el-option
v-for="item in brandOfficeDomainDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</td>
</tr>
<tr>
<td class="label-center">
<ele-text class="label-required" tag="span"> 工作室具体位置 </ele-text>
</td>
<td :colspan="6">
<el-form-item
label=""
prop="address"
label-width="0px"
label-position="left"
:style="{ marginBottom: '0px' }"
class="pro-form-error-popper"
>
<el-input
:clearable="true"
type="text"
placeholder=""
:maxlength="80"
:show-word-limit="true"
:show-password="false"
v-model="form.address"
/>
</el-form-item>
</td>
</tr>
<tr>
<td class="label-center">
<ele-text class="label-required" tag="span"> 工作室基本情况介绍 </ele-text>
</td>
<td :colspan="6">
<el-form-item prop="introduction" label-width="0px" label-position="left">
<el-input
:rows="20"
placeholder="请输入工作室基本情况介绍"
:show-word-limit="true"
:maxlength="1500"
type="textarea"
v-model="form.introduction"
/>
</el-form-item>
</td>
</tr>
<tr>
<td :colspan="7" :style="{ 'text-align': 'center' }">
<div
:style="{
position: 'relative',
display: 'flex',
'align-items': 'center',
'justify-content': 'center'
}"
>
<ele-text tag="span" type="default">
工作室成员基本情况
</ele-text>
<div v-if="editMode" :style="{ right: '0', position: 'absolute' }">
<select-member-btn
v-model:unionMembers="form.brandStudioMembers"
:top-union-id="topUnionId"
/>
</div>
</div>
</td>
</tr>
<colgroup v-if="editMode">
<col style="width: 120px" />
<col style="width: 140px" />
<col style="width: 150px" />
<col style="width: 120px" />
<col style="width: 120px" />
<col style="width: 240px" />
<col style="width: 80px" />
</colgroup>
<colgroup v-if="!editMode">
<col style="width: 180px" />
<col style="width: 210px" />
<col style="width: 230px" />
<col style="width: 170px" />
<col style="max-width: 180px" />
</colgroup>
<el-form-item prop="brandStudioMembers" style="display: none;" />
<tr>
<td class="label-center">
<ele-text tag="span"> 姓名 </ele-text>
</td>
<td class="label-center">
<ele-text class="label-required" tag="span"> 出生年月 </ele-text>
</td>
<td class="label-center">
<ele-text class="label-required" tag="span"> 学历 </ele-text>
</td>
<td :colspan="2" :style="{ minWidth: '160px' }" class="label-center">
<ele-text class="label-required" tag="span"> 职称 </ele-text>
</td>
<td :colspan="editMode ? 1 : 2" :style="{ width: '220px' }" class="label-center">
<ele-text tag="span"> 所在部门 </ele-text>
</td>
<td v-if="editMode" :style="{ width: '70px' }" class="label-center">
<ele-text tag="span"> 操作 </ele-text>
</td>
</tr>
<tr
v-for="(row, idx) in form.brandStudioMembers"
:key="idx"
>
<td class="union-member">
<ele-text tag="span"> {{ row.name }} </ele-text>
</td>
<td class="union-member">
<el-form-item
v-if="StrUtil.isNotBlank(row.name)"
:prop="`brandStudioMembers.${idx}.birthdate`"
label-width="0px"
:rules="rulesMember.birthdate"
>
<el-date-picker
value-format="YYYY-MM"
placeholder="选择年月"
format="YYYY-MM"
v-model="row.birthdate"
/>
</el-form-item>
<ele-text v-else tag="span"> {{ row.birthdate }} </ele-text>
</td>
<td class="union-member">
<el-form-item
v-if="StrUtil.isNotBlank(row.name)"
:prop="`brandStudioMembers.${idx}.grade`"
label-width="0px"
:rules="rulesMember.grade"
>
<el-select
class="ele-fluid"
:clearable="true"
placeholder="请选择学历类型"
v-model="row.grade"
>
<el-option
v-for="item in userEducationDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
<ele-text v-else tag="span"> {{ row.grade }} </ele-text>
</td>
<td class="union-member" :colspan="2">
<el-form-item
v-if="StrUtil.isNotBlank(row.name)"
:prop="`brandStudioMembers.${idx}.professional`"
label-width="0px"
label-position="right"
:rules="rulesMember.professional"
>
<el-input
:clearable="true"
type="text"
placeholder="请输入成员职称"
:maxlength="30"
:show-word-limit="true"
v-model="row.professional"
/>
</el-form-item>
<ele-text v-else tag="span"> {{ row.professional }} </ele-text>
</td>
<td class="union-member" :colspan="editMode ? 1 : 2" :style="{ minWidth: '220px' }">
<ele-text tag="span"> {{ row.deptName }} </ele-text>
</td>
<td v-if="editMode" :style="{ minWidth: '70px', textAlign: 'center' }">
<el-button
type="danger"
size="default"
:plain="true"
:text="false"
:bg="false"
@click="handleRemoveMember(idx)"
>
删除
</el-button>
</td>
</tr>
</ele-table>
</el-form>
</div>
</template>
<script setup>
import {ref, reactive, nextTick, watch, onMounted, computed} from 'vue';
import { useFormData } from '@/utils/use-form-data';
import {get2union} from "@/api/club/apply/index.js";
import {useDictData} from "@/utils/use-dict-data.js";
import SurveyTreeSelect from "@/views/survey/components/survey-tree-select.vue";
import {
addBrandStudioApplicationApi,
getLeaderHonorApi,
updateBrandStudioApplicationApi
} from "@/api/office/brand/index.js";
import {EleMessage, EleTable} from "ele-admin-plus";
import SelectMemberBtn from "@/views/brand/components/select-member-btn.vue";
import {StrUtil} from "@/utils/toolkit.js";
import dayjs from "dayjs";
import {ElMessageBox} from "element-plus";
const props = defineProps({
editMode: Boolean,
loading: Boolean,
userUnionInfo: Object, // 用户工会相关信息
topUnionId: String, // 最上层工会ID
brandForm: Object // 表格信息,修改时使用
})
const emit = defineEmits(['update:loading', 'editFinish'])
/** 字典数据 */
const [
leadPeopleDict,
brandOfficeTypeDict,
userEducationDict,
brandOfficeDomainDict
] = useDictData([
'lead_people',
'brand_office_type',
'user_education',
'brand_office_domain'
]);
/** 表单组件 */
const formRef = ref(null);
/** 表单数据(只改字段命名) */
const [form, resetFields, assignFields] = useFormData({
// 基本信息
id: '',
unionId: void 0, // 所属单位工会
unionName: void 0, // 所属单位工会
deptId: void 0,
deptName: void 0,
name: void 0, // 工作室名称
createYear: void 0, // 创建年度
leadPeople: void 0, // 工作室领衔人
leadAge: 0, // 领衔人年龄
leadIdentity: void 0, // 领衔人身份
memberNum: 3, // 成员人数
type: void 0, // 工作室类型
track: void 0, // 工作室赛道
address: void 0, // 工作室具体位置
introduction: void 0, // 工作室基本情况介绍
brandStudioMembers: [
{ name: void 0, birthdate: void 0, grade: void 0, professional: void 0, deptName: void 0 },
{ name: void 0, birthdate: void 0, grade: void 0, professional: void 0, deptName: void 0 },
{ name: void 0, birthdate: void 0, grade: void 0, professional: void 0, deptName: void 0 }
]
});
// 成员信息校验
const rulesMember = reactive({
birthdate: [
{ required: true, message: '请选择出生年月', trigger: ['change', 'blur'] },
{
validator: (_, v, cb) => {
if (StrUtil.isBlank(v)) return cb(new Error('请选择出生年月'));
const s = String(v).trim();
if (!/^\d{4}-(0[1-9]|1[0-2])$/.test(s)) {
return cb(new Error('出生年月格式应为 YYYY-MM'));
}
// 不能大于当前月份(可按业务改)
const d = dayjs(s, 'YYYY-MM', true);
if (!d.isValid()) return cb(new Error('出生年月不合法'));
if (d.isAfter(dayjs(), 'month')) return cb(new Error('出生年月不能晚于当前月份'));
cb();
},
trigger: ['change', 'blur']
}
],
grade: [
{ required: true, message: '请选择学历', trigger: ['change', 'blur'] }
],
professional: [
{ required: true, message: '请输入职称', trigger: ['blur', 'change'] },
{
validator: (_, v, cb) => {
if (StrUtil.isBlank(v)) return cb(new Error('请输入职称'));
const s = String(v).trim();
if (s.length > 30) return cb(new Error('职称长度不能超过 30'));
// 常见职称字符集(中英数/空格/.-/括号)
const ok = /^[\u4e00-\u9fa5A-Za-z0-9\s.\-()()]+$/.test(s);
if (!ok) return cb(new Error('职称包含非法字符'));
cb();
},
trigger: ['blur', 'change']
}
],
});
/** 表格列配置 */
const columns = computed(() => [
{ prop: 'name', label: '姓名', align: 'center', minWidth: 80, slot: 'name' },
{ prop: 'birthdate', label: '出生年月', align: 'center', minWidth: 120, slot: 'birthdate', required: true },
{ prop: 'grade', label: '学历', align: 'center', minWidth: 80, slot: 'grade', required: true },
{ prop: 'professional', label: '职称', align: 'center', minWidth: 100, slot: 'professional', required: true },
{ prop: 'deptName', label: '所在部门', align: 'center', minWidth: 100, slot: 'deptName' },
{ columnKey: 'action', label: '操作', width: 80, align: 'center', slot: 'action', notRequired: true }
]);
const fieldLabelMap = {};
columns.value.forEach((col) => {
fieldLabelMap[col.prop] = col.label;
});
/** 表单验证规则(同步改 prop key) */
const rules = reactive({
unionId: [
{ required: true, message: '所属单位工会', trigger: 'change' }
],
name: [
{ required: true, message: '请输入工作室名称', trigger: 'blur' }
],
leadPeople: [
{ required: true, message: '请选择领衔人', trigger: 'change' }
],
leadAge: [
{ required: true, message: '请输入年龄', trigger: 'change' }
],
leadIdentity: [
{ required: true, message: '请选择领衔人身份', trigger: 'change', type: 'array' }
],
memberNum: [
{ required: true, message: '请输入成员人数', trigger: 'change' }
],
type: [
{ required: true, message: '请选择工作室类型', trigger: 'change' }
],
track: [
{ required: true, message: '请选择所属赛道', trigger: 'change' }
],
address: [
{ required: true, message: '请输入工作室具体位置', trigger: 'blur' }
],
introduction: [
{ required: true, message: '请输入工作室基本情况介绍', trigger: 'blur' }
],
brandStudioMembers: {
type: 'array',
required: true,
trigger: 'blur',
validator: (rule, value, callback) => {
const members = value.filter(item => StrUtil.isNotBlank(item.name))
console.log(value)
// 1. 必须是数组且长度 >= 1
if (!Array.isArray(members) || members.length < 1) {
ElMessageBox.alert(
`请至少选择1位工作室成员!`,
`系统提示`, {
confirmButtonText: '确定',
type: 'warning',
});
return callback(new Error('请至少选择1位工作室成员'));
}
// 2. 检查每个成员的必填字段
const requiredFields = columns.value
.filter((col) => col.notRequired !== true)
.map((col) => col.prop); // ['name', 'birthdate', ...]
for (let i = 0; i < value.length; i++) {
const item = value[i];
for (const field of requiredFields) {
const val = item?.[field];
if (StrUtil.isEmpty(val)) {
const label = fieldLabelMap[field] || field; // fallback to field name
ElMessageBox.alert(
`${item.name}】成员的【${label}】不能为空!`,
`系统提示`, {
confirmButtonText: '确定',
type: 'warning',
});
return callback(
new Error(`${i + 1} 位成员的“${label}”不能为空`)
);
}
}
}
callback();
}
}
});
/** 领衔人荣誉 */
const leaderHonors = ref([]);
const leaderChange = (emplid) => {
console.log('emplid: ' + emplid);
const matchedItem = leaderHonors.value.find(
(item) => item.emplid === emplid
);
console.log(matchedItem)
if (matchedItem) {
form.leadHonor = matchedItem.honorNames?.join(',') || '';
form.leadAge = matchedItem.age || 0;
form.leadPeople = matchedItem.userName || '';
} else {
form.leadHonor = '';
form.leadAge = 0;
}
};
const handleRemoveMember = (idx) => {
form.brandStudioMembers.splice(idx, 1);
form.memberNum = form.brandStudioMembers.length;
// 可选:删完后清一下校验(避免残留错误)
nextTick(() => formRef.value?.clearValidate?.());
};
const unionAssign = (item) => {
if (!item) {
form.unionName = '';
return;
}
form.unionName = item.name;
}
const userUnionId = ref('')
// 获取成员当前所在工会和所有工会
const assignUserUnion = (userUnionInfo) => {
form.unionId = userUnionInfo.unionId;
form.unionName = userUnionInfo.unionName;
form.deptId = userUnionInfo.deptId;
form.deptName = userUnionInfo.deptName;
form.createYear = new Date().getFullYear().toString();
}
watch(() => props.userUnionInfo,
(newVal, oldVal) => {
if (StrUtil.isBlank(newVal?.unionId)) {
return;
}
assignUserUnion(props.userUnionInfo);
}, {immediate: true})
watch(
() => form.brandStudioMembers.length,
(len) => {
form.memberNum = len
}, { immediate: true }
)
watch(
() => props.brandForm,
(newVal, oldVal) => {
console.log('newVal---', newVal)
if (StrUtil.isBlank(newVal?.id)) {
return
}
const data = {
...newVal,
leadIdentity: JSON.parse(newVal?.leadIdentity)
}
assignFields(data)
}, {immediate: true}
)
onMounted(async () => {
console.log('mounted');
// 调用接口获取所有工会列表并转为tree
get2union().then((res) => {
userUnionId.value = res.data
})
const res = await getLeaderHonorApi();
leaderHonors.value = res.data.map((item, index) => {
return {
id: index,
emplid: item.emplid,
userName: item.userName,
age: item.age,
honorNames: item.honorNames
};
});
});
// 提交数据/保存草稿
const submit = (item) => {
form.status = item.saveType;
const params = { ...form };
params.leadIdentity = JSON.stringify(form.leadIdentity) || {};
params.domain = params.track;
formRef.value?.validate?.((valid) => {
if (!valid) {
emit('update:loading', false)
return;
}
const saveOrUpdate = item.isUpdate
? updateBrandStudioApplicationApi
: addBrandStudioApplicationApi;
saveOrUpdate(params)
.then(() => {
EleMessage.success({ message: '保存成功', plain: true });
emit('editFinish')
})
.catch((err) => {
EleMessage.error({ message: '服务错误', plain: true });
console.warn(err);
})
.finally(() => {
emit('update:loading', false)
});
})
}
defineExpose({
submit
})
</script>
<style scoped>
.el-form-item {
margin-bottom: 0;
}
.label-center, .union-member {
text-align: center;
}
/* 基础尺寸:和 el-checkbox 完全一致 */
.radio-as-checkbox :deep(.el-radio__inner) {
box-sizing: border-box;
width: 16px;
height: 16px;
border-radius: 4px;
}
/* 干掉 radio 原点 */
.radio-as-checkbox :deep(.el-radio__inner::after) {
display: none;
}
/* hover 边框 */
.radio-as-checkbox :deep(.el-radio__input:not(.is-disabled):hover .el-radio__inner) {
border-color: var(--el-color-primary);
width: 16px;
height: 16px;
border-radius: 4px;
}
/* 选中态:背景 + 边框 */
.radio-as-checkbox :deep(.el-radio__input.is-checked .el-radio__inner) {
background-color: var(--el-color-primary);
border-color: var(--el-color-primary);
}
/* el-checkbox 同款对勾 */
.radio-as-checkbox :deep(.el-radio__input.is-checked .el-radio__inner::before) {
content: '';
position: absolute;
left: 4px;
bottom: 3px;
width: 4px;
height: 8px;
border: 2px solid #fff;
border-top: none;
border-left: none;
transform: rotate(45deg);
}
/* disabled 状态(和 el-checkbox 对齐) */
.radio-as-checkbox :deep(.el-radio__input.is-disabled .el-radio__inner) {
background-color: var(--el-fill-color-light);
border-color: var(--el-border-color-light);
}
.radio-as-checkbox :deep(.el-radio__input.is-disabled.is-checked .el-radio__inner) {
background-color: var(--el-color-primary-light-5);
border-color: var(--el-color-primary-light-5);
}
/* 只放开人员表格里的 td */
.union-member {
overflow: visible !important;
}
.union-member :deep(.el-select__selected-item),
.union-member :deep(.el-input__inner),
.union-member :deep(.el-input__wrapper .el-input__inner) {
text-align: center;
}
.label-required::before {
content: '*';
color: var(--el-color-danger);
}
</style>
@@ -0,0 +1,27 @@
import {h} from "vue";
export const withRequiredHeader = (col) => ({
...col,
renderHeader: () => {
if (!col.required) return col.label;
return h(
'span',
{ style: { display: 'inline-flex', alignItems: 'center' } },
[
h('span', null, col.label),
h(
'span',
{
style: {
color: '#f56c6c', // 红色
fontWeight: '700',
fontSize: '16px',
marginLeft: '4px' // 放右侧留间距
}
},
'*'
)
]
);
}
});
@@ -0,0 +1,72 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<el-form :inline="true" :model="pageForm">
<el-form-item label="工作室名称">
<el-input v-model="pageForm.name" clearable></el-input>
</el-form-item>
<el-form-item label="创建年份">
<el-date-picker v-model="pageForm.createYear" type="year" value-format="yyyy"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="pageData">查询</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" border>
<el-table-column type="index" label="序号" width="60"></el-table-column>
<el-table-column prop="name" label="工作室名称" min-width="160"></el-table-column>
<el-table-column prop="createYear" label="创立年度" width="100"></el-table-column>
<el-table-column prop="leadPeople" label="领衔人" width="120"></el-table-column>
<el-table-column prop="deptName" label="所属机构" min-width="140"></el-table-column>
<el-table-column prop="type" label="工作室类型" width="120"></el-table-column>
<el-table-column prop="track" label="工作室赛道" width="120"></el-table-column>
<el-table-column prop="memberNum" label="成员人数" width="90"></el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="{row}">
<el-button type="text" @click="openApply(row.id)">编辑工作室</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
pageForm: {
page: 1,
limit: 20,
status: 5,
createYear: String(new Date().getFullYear())
},
tableData: []
}
},
methods: {
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
pageData() {
this.$axios.get('/platform/zhgh/brand/application/page', {params: this.pageForm}).then((res) => {
const data = this.getData(res) || {}
this.tableData = data.list || data.data || []
})
},
openApply(id) {
window.location.href = '/platform/zhgh/brand/apply/index?id=' + id
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,205 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<param-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
highlight-current-row
:export-config="{ fileName: '参数设置' }"
cache-key="systemConfigTable"
:tools="false"
>
<template #createTime="{ row }">
{{ dayjs(row.createTime).format('YYYY-MM-DD') }}
</template>
<template #leadIdentity="{ row }">
<div
style="display: flex"
v-for="(item, index) in JSON.parse(row.leadIdentity)"
:key="index"
>
<el-tag type="primary">
{{ item.dictLabel }}
</el-tag>
</div>
</template>
<template #memberNum="{ row }">
{{ row.memberNum + '人' }}
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="openEdit(row)"
:icon="EditOutlined"
>
编辑工作室
</el-link>
</template>
</ele-pro-table>
</ele-card>
</ele-page>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import ParamSearch from '../components/param-search.vue';
import dayjs from 'dayjs';
import {
getBrandStudioApplicationPageApi,
getUserBelongUnionApi
} from '@/api/office/brand/index.js';
import { EditOutlined } from '@/components/icons/index.js';
import { BRAND_EDIT_PATH } from '@/config/setting.js';
import { useRouter } from 'vue-router';
const { push } = useRouter();
import { usePageTab } from '@/utils/use-page-tab';
const { addPageTab } = usePageTab();
defineOptions({ name: 'Application' });
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => {
return [
// {
// type: 'selection',
// columnKey: 'selection',
// width: 50,
// align: 'center'
// },
{
type: 'index',
columnKey: 'index',
width: 60,
align: 'center',
label: '序号'
},
{
prop: 'name',
label: '工作室名称',
align: 'center',
minWidth: 180
},
{
prop: 'createYear',
label: '创立年度',
align: 'center',
minWidth: 80,
slot: 'createYear'
},
{
prop: 'leadPeople',
label: '领衔人',
minWidth: 100,
align: 'center',
slot: 'leadPeople'
},
{
prop: 'deptName',
label: '所属单位机构',
minWidth: 140,
align: 'center'
},
{
prop: 'type',
label: '工作室类型',
align: 'center',
minWidth: 110,
slot: 'type'
},
{
prop: 'track',
label: '工作室赛道',
align: 'center',
minWidth: 110,
slot: 'track'
},
{
prop: 'memberNum',
label: '成员人数',
align: 'center',
minWidth: 80,
slot: 'memberNum'
},
{
prop: 'address',
label: '工作室地址',
minWidth: 140,
align: 'center',
slot: 'address'
},
{
prop: 'createTime',
label: '申请时间',
minWidth: 100,
align: 'center',
slot: 'createTime'
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
];
});
/** 表格选中数据 */
const selections = ref([]);
/** 表格数据源 */
const datasource = async ({ pages, where, filters }) => {
where = {
...where,
status: 5,
createYear: new Date().getFullYear().toString(),
};
const { data } = await getBrandStudioApplicationPageApi({
...where,
...filters,
...pages
});
return data;
};
const unionName = ref('');
onMounted(async () => {
console.log('mounted');
const { data } = await getUserBelongUnionApi();
unionName.value = data.name;
});
/** 搜索 */
const reload = (where) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 打开编辑弹窗 */
const openEdit = (row) => {
const path = BRAND_EDIT_PATH + '/' + row.id + '/edit';
addPageTab({
title: '工作室管理',
key: path,
closable: true,
meta: { icon: 'LinkOutlined' }
});
push(path);
};
</script>
@@ -0,0 +1,145 @@
<!-- 项目进度 -->
<template>
<ele-card :header="title" :body-style="{ padding: '10px', height: '370px' }">
<ele-pro-table
:height="352"
row-key="id"
:columns="columns"
:datasource="applyWarning"
:show-overflow-tooltip="true"
highlight-current-row
:pagination="false"
:toolbar="false"
:bottom-line="false"
size="large"
class="project-table"
>
<template #pendingDays="{ row }">
<span v-if="Math.floor(row.pendingDays) === 0">
{{ Math.round(row.pendingDays * 24) + '小时' }}
</span>
<span v-else>
{{ Math.round(row.pendingDays * 100) / 100 + '' }}
</span>
</template>
<template #warnStatus="{ row }">
<ele-text v-if="row.warnStatus === 1" type="default">待处理</ele-text>
<ele-text v-else-if="row.warnStatus === 2" type="primary"
>关注</ele-text
>
<ele-text v-else-if="row.warnStatus === 3" type="warning"
>预警</ele-text
>
<ele-text v-else-if="row.warnStatus === 4" type="info">严重</ele-text>
<ele-text v-else type="danger">紧急</ele-text>
</template>
<template #action="{ row }">
<el-link
v-if="row.status !== 0"
v-permission="'system:config:edit'"
type="primary"
underline="never"
:icon="FileOutlined"
>
<span @click="handleCommand(row)"> 审批 </span>
</el-link>
</template>
</ele-pro-table>
</ele-card>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { getApplyWarningStatsApi } from '@/api/office/statistics/index.js';
import { FileOutlined } from '@/components/icons/index.js';
defineProps({
title: String
});
/** 表格列配置 */
const columns = ref([
{
prop: 'name',
label: '品牌工作室名称',
minWidth: 110
},
{
prop: 'unionName',
label: '直属工会',
align: 'center',
minWidth: 110
},
{
prop: 'leadPeople',
label: '领衔人',
align: 'center',
minWidth: 110
},
{
prop: 'receiveTime',
label: '接收时间',
align: 'center',
minWidth: 110
},
{
prop: 'pendingDays',
label: '已提交时间',
width: 110,
align: 'center',
slot: 'pendingDays'
},
{
prop: 'warnStatus',
label: '审核预警',
width: 110,
align: 'center',
slot: 'warnStatus'
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
]);
/** 申请预警数据表 */
const applyWarning = ref([]);
const emit = defineEmits(['command']);
const handleCommand = (command) => {
emit('command', {
...command,
id: command.applicationId,
op: 'applyWarning'
});
};
/** 查询申请进度数据 */
const queryApplyWarningList = () => {
getApplyWarningStatsApi()
.then((res) => {
// console.log(res);
applyWarning.value = res.data;
})
.catch((err) => {
console.log(err);
});
};
onMounted(() => {
queryApplyWarningList();
});
</script>
<style lang="scss" scoped>
.project-table :deep(.el-progress__text) {
font-size: 12px !important;
}
</style>
@@ -0,0 +1,144 @@
<!-- 项目进度 -->
<template>
<ele-card :header="title" :body-style="{ padding: '10px', height: '370px' }">
<ele-pro-table
:height="352"
row-key="id"
:columns="columns"
:datasource="applyProgress"
:show-overflow-tooltip="true"
highlight-current-row
:pagination="false"
:toolbar="false"
:bottom-line="false"
size="large"
class="project-table"
>
<template #projectName="{ row }">
<el-link type="primary" underline="never">
{{ row.projectName }}
</el-link>
</template>
<template #status="{ row }">
<ele-text v-if="row.status === 0" type="danger">待申请</ele-text>
<ele-text v-else-if="row.status === 1" type="primary">
审核中
</ele-text>
<ele-text v-else-if="[2, 4].includes(row.status)" type="warning">
待修改
</ele-text>
<ele-text v-else type="success">已完成</ele-text>
</template>
<template #progressPct="{ row }">
<el-progress :percentage="row.progressPct" />
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="handleCommand(row)"
:icon="FileOutlined"
>
查看
</el-link>
</template>
</ele-pro-table>
</ele-card>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { getApplyProgressStatsApi } from '@/api/office/statistics/index.js';
import { FileOutlined } from '@/components/icons/index.js';
defineProps({
title: String
});
/** 表格列配置 */
const columns = ref([
{
prop: 'applicationName',
label: '品牌工作室名称',
minWidth: 110
},
{
prop: 'unionName',
label: '直属工会',
align: 'center',
minWidth: 110
},
{
prop: 'leadPeople',
label: '领衔人',
align: 'center',
minWidth: 110
},
{
prop: 'createTime',
label: '创建时间',
align: 'center',
minWidth: 110
},
{
prop: 'status',
label: '状态',
slot: 'status',
align: 'center',
width: 90
},
{
prop: 'progressPct',
label: '进度',
width: 180,
align: 'center',
slot: 'progressPct',
showOverflowTooltip: false
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
]);
/** 申请进度数据表 */
const applyProgress = ref([]);
const emit = defineEmits(['command']);
const handleCommand = (command) => {
emit('command', {
...command,
id: command.applicationId,
op: 'applyProgress'
});
};
/** 查询申请进度数据 */
const queryApplyProgressList = () => {
getApplyProgressStatsApi()
.then((res) => {
applyProgress.value = res.data;
})
.catch((err) => {
console.log(err);
});
};
onMounted(() => {
queryApplyProgressList();
});
</script>
<style lang="scss" scoped>
.project-table :deep(.el-progress__text) {
font-size: 12px !important;
}
</style>
@@ -0,0 +1,81 @@
<template>
<ele-card :header="title" :body-style="{ height: '370px' }">
<div class="workplace-goal">
<v-chart
ref="saleChartRef"
:style="{ height: height }"
:option="saleChartOption"
/>
</div>
</ele-card>
</template>
<script setup>
import { ref, provide, computed } from 'vue';
import VChart, { THEME_KEY } from 'vue-echarts';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { BarChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { useEcharts } from '@/utils/use-echarts';
import { useBodyResize } from '@/utils/use-body-resize.js';
import { ChartTheme } from 'ele-admin-plus';
use([CanvasRenderer, BarChart, GridComponent, TooltipComponent]);
const props = defineProps({
title: String,
data: Object,
height: {
type: String,
default: () => '320px'
},
grid: {
type: Object,
default: () => {
return {left: 50, right: 20, top: 20, bottom: 70}
}
},
xAxisLabel: {
type: Object,
default: () => {
return {
interval: 0,
rotate: 25,
formatter: (val) => {
const text = String(val ?? '');
return text.length > 10 ? `${text.slice(0, 10)}` : text;
}
}
}
}
});
// 图表ref
const saleChartRef = ref(null);
// 传递图表的ref, 数组形式, 可传多个
useEcharts([saleChartRef]);
// EleAdminPlus提供的主题, 对颜色间距等都做了默认设置
provide(THEME_KEY, ChartTheme);
// 在侧栏折叠展开以及浏览器窗口大小改变后重置尺寸
useBodyResize(() => {
saleChartRef.value?.resize?.();
});
// 柱状图配置
const saleChartOption = computed(() => {
return {
tooltip: { trigger: 'axis' },
grid: props.grid,
xAxis: [
{
type: 'category',
data: props.data.x || [],
axisLabel: props.xAxisLabel
}
],
yAxis: [{ type: 'value' }],
series: [{ type: 'bar', data: props.data.y || [] }]
};
});
</script>
@@ -0,0 +1,61 @@
<!-- 本月目标 -->
<template>
<ele-card :header="title" :body-style="{ height: '370px' }">
<div class="workplace-goal">
<el-progress
:width="180"
:percentage="80"
color="var(--el-color-primary)"
type="dashboard"
:format="() => ''"
/>
<div class="workplace-goal-body">
<el-tag
size="large"
:disable-transitions="true"
style="width: 36px; height: 36px; border-radius: 50%; line-height: 0"
>
<el-icon style="cursor: default; border-radius: 0">
<TrophyBase />
</el-icon>
</el-tag>
<div style="font-size: 40px">285</div>
</div>
<div>恭喜, 本月目标已达标!</div>
</div>
</ele-card>
</template>
<script setup>
import { TrophyBase } from '@element-plus/icons-vue';
defineProps({
title: String
});
const emit = defineEmits(['command']);
const handleCommand = (command) => {
emit('command', command);
};
</script>
<style lang="scss" scoped>
.workplace-goal {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
.workplace-goal-body {
position: absolute;
top: 50%;
left: 50%;
width: 180px;
margin: -48px 0 0 -90px;
text-align: center;
}
}
</style>
@@ -0,0 +1,62 @@
<template>
<ele-card :header="title" :body-style="{ height: '370px' }">
<div class="workplace-goal">
<v-chart
ref="saleChartRef"
style="height: 320px"
:option="saleChartOption"
/>
</div>
</ele-card>
</template>
<script setup>
import { ref, provide, computed } from 'vue';
import VChart, { THEME_KEY } from 'vue-echarts';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { LineChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { useEcharts } from '@/utils/use-echarts';
import { useBodyResize } from '@/utils/use-body-resize.js';
import { ChartTheme } from 'ele-admin-plus';
use([CanvasRenderer, LineChart, GridComponent, TooltipComponent]);
const props = defineProps({
title: String,
data: Object
});
// 图表ref
const saleChartRef = ref(null);
// 传递图表的ref, 数组形式, 可传多个
useEcharts([saleChartRef]);
// EleAdminPlus提供的主题, 对颜色间距等都做了默认设置
provide(THEME_KEY, ChartTheme);
// 在侧栏折叠展开以及浏览器窗口大小改变后重置尺寸
useBodyResize(() => {
saleChartRef.value?.resize?.();
});
// 柱状图配置
const saleChartOption = computed(() => {
return {
tooltip: { trigger: 'axis' },
xAxis: [
{
type: 'category',
data: props.data.x
}
],
yAxis: [{ type: 'value' }],
series: [
{
type: 'line',
smooth: true,
data: props.data.y
}
]
}
});
</script>
@@ -0,0 +1,76 @@
<template>
<ele-card :header="title" :body-style="{ height: '370px' }">
<div class="workplace-goal">
<v-chart
ref="saleChartRef"
:style="{ height: height }"
:option="saleChartOption"
/>
</div>
</ele-card>
</template>
<script setup>
import { ref, computed, provide, onMounted } from 'vue';
import VChart, { THEME_KEY } from 'vue-echarts';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { PieChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { useEcharts } from '@/utils/use-echarts';
import { useBodyResize } from '@/utils/use-body-resize.js';
import { ChartTheme } from 'ele-admin-plus';
use([CanvasRenderer, PieChart, GridComponent, TooltipComponent]);
const props = defineProps({
title: {
type: String,
default: () => ''
},
data: {
type: Array,
default: () => []
},
height: {
type: String,
default: () => '320px'
},
});
// 图表ref
const saleChartRef = ref(null);
// 传递图表的ref, 数组形式, 可传多个
useEcharts([saleChartRef]);
// EleAdminPlus提供的主题, 对颜色间距等都做了默认设置
provide(THEME_KEY, ChartTheme);
// 在侧栏折叠展开以及浏览器窗口大小改变后重置尺寸
useBodyResize(() => {
saleChartRef.value?.resize?.();
});
// 柱状图配置
const saleChartOption = computed(() => ({
tooltip: {
trigger: 'item'
},
series: [
{
name: props.title,
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {d}%'
},
data: props.data || []
}
]
}));
</script>
@@ -0,0 +1,114 @@
<!-- 项目进度 -->
<template>
<ele-card
:header="title"
:body-style="{ padding: '10px', minHeight: '150px' }"
>
<ele-pro-table
row-key="id"
:columns="columns"
:datasource="unionApplies"
:show-overflow-tooltip="true"
highlight-current-row
:pagination="false"
:toolbar="false"
:bottom-line="false"
size="large"
class="project-table"
>
<template #passRate="{ row }">
<el-link type="primary" underline="never">
{{ row.passRate + '%' }}
</el-link>
</template>
<template #repealRate="{ row }">
<el-link type="danger" underline="never">
{{ row.repealRate + '%' }}
</el-link>
</template>
</ele-pro-table>
</ele-card>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { getUnionApplyStatsApi } from '@/api/office/statistics/index.js';
defineProps({
title: String
});
const emit = defineEmits(['command']);
/** 表格列配置 */
const columns = ref([
{
type: 'index',
columnKey: 'index',
width: 50,
align: 'center'
},
{
prop: 'unionName',
label: '工会名称',
slot: 'unionName',
minWidth: 110
},
{
prop: 'applyCnt',
label: '申请总数',
align: 'center'
},
{
prop: 'passedCnt',
label: '通过总数',
align: 'center'
},
{
prop: 'reviewCnt',
label: '待审批数',
align: 'center'
},
{
prop: 'rejectCnt',
label: '驳回数',
align: 'center'
},
{
prop: 'repealCnt',
label: '撤销数',
align: 'center'
},
{
prop: 'passRate',
label: '通过率',
align: 'center',
slot: 'passRate'
},
{
prop: 'repealRate',
label: '撤销率',
align: 'center',
slot: 'repealRate'
}
]);
/** 工会审核指标数据 */
const unionApplies = ref([]);
/** 查询工会审核指标数据 */
const unionApplyStats = () => {
getUnionApplyStatsApi()
.then((res) => {
const { data } = res;
unionApplies.value = data;
})
.catch((err) => {
console.error(err);
});
};
onMounted(() => {
unionApplyStats();
});
</script>
@@ -0,0 +1,236 @@
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import GoalCard from './components/goal-card.vue';
import PieChart from './components/pie-chart.vue';
import UnionTable from './components/union-table.vue';
import ApplyProgress from './components/apply-progress.vue';
import ApplyPending from './components/apply-pending.vue';
import BarChart from './components/bar-chart.vue';
import LineChart from './components/line-chart.vue';
import SortableJs from 'sortablejs';
import FlowAudit from '@/views/brand/components/flow-audit.vue';
import { getApplyCountStatsApi } from '@/api/office/statistics/index.js';
defineOptions({
name: 'Overview',
components: {
GoalCard,
PieChart,
UnionTable,
ApplyProgress,
ApplyPending,
LineChart,
BarChart
}
});
/** 卡片数据 */
const data = ref([
{
name: 'pie-chart',
title: '申请情况(个人)',
data: [
{ value: 1048, name: '通过' },
{ value: 735, name: '待审批' },
{ value: 580, name: '待提交' },
{ value: 484, name: '驳回' },
{ value: 300, name: '失败' }
],
md: 8,
sm: 24,
xs: 24
},
{
name: 'apply-progress',
title: '申请进度记录(个人)',
md: 16,
sm: 24,
xs: 24
},
{
name: 'pie-chart',
title: '年龄分布(组织)',
data: [
{ value: 1048, name: '<20' },
{ value: 735, name: '20-40' },
{ value: 580, name: '40-60' },
{ value: 256, name: '60+' }
],
md: 8,
sm: 24,
xs: 24
},
{
name: 'apply-pending',
title: '工会平均审核时长和预警(组织)',
md: 16,
sm: 24,
xs: 24
},
{
name: 'bar-chart',
title: '工作室类型统计(个人/组织)',
label: 'type',
data: [],
md: 8,
sm: 24,
xs: 24
},
{
name: 'line-chart',
title: '年度申请数统计(个人/组织)',
label: 'createYear',
data: [],
md: 8,
sm: 24,
xs: 24
},
{
name: 'union-table',
title: '工会审核情况统计(组织)',
md: 24,
sm: 24,
xs: 24
}
]);
/** 拖拽排序实例 */
let sortableIns = null;
/** 容器 */
const wrapRef = ref(null);
/** 当前审核流数据 */
const current = ref(null);
/** 显示审核流界面 */
const showFlowCurrent = ref(false);
/** 审核流界面是否可以编辑 */
const isAudit = ref(false);
/** 编辑卡片 */
const handleCommand = (command, index) => {
switch (command.op) {
case 'applyProgress': // 刷新
current.value = command;
isAudit.value = false;
showFlowCurrent.value = true;
break;
case 'applyWarning':
current.value = command;
isAudit.value = true;
showFlowCurrent.value = true;
break;
}
};
const applyCountObject = ref({});
/** 统计用户申请的各字段分组 */
const queryApplyCountStats = () => {
getApplyCountStatsApi().then((res) => {
console.log(res.data);
applyCountObject.value = res.data;
for (const element of data.value) {
if (element.label) {
const label = element.label;
const item = res.data[label];
element.data = {
x: Object.keys(item), // ['公益活动', '社会实践', '社会实践报告', '科学科研', '绿色环保']
y: Object.values(item) // [1, 1, 1, 1, 1]
};
}
}
console.log(data.value);
});
};
onMounted(() => {
queryApplyCountStats();
// 卡片支持拖动排序
sortableIns = new SortableJs(wrapRef.value?.$el, {
handle: '.ele-card-header',
filter: '.demo-more-icon',
animation: 300,
delay: 150,
delayOnTouchOnly: true,
onUpdate: ({ oldIndex, newIndex }) => {
if (typeof oldIndex === 'number' && typeof newIndex === 'number') {
const temp = [...data.value];
temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]);
data.value = temp;
}
},
setData: () => {},
forceFallback: true
});
});
onBeforeUnmount(() => {
sortableIns && sortableIns.destroy();
sortableIns = null;
});
</script>
<template>
<ele-page>
<el-row :gutter="16" ref="wrapRef">
<el-col
v-for="(item, index) in data"
:key="item.name"
:md="item.md"
:sm="item.sm"
:xs="item.xs"
>
<component
:is="item.name"
:title="item.title"
:data="item.data"
@command="(command) => handleCommand(command, index)"
/>
</el-col>
</el-row>
<flow-audit v-model="showFlowCurrent" :data="current" :is-audit="isAudit" />
</ele-page>
</template>
<style lang="scss" scoped>
.workplace-page {
:deep(.ele-card-header) {
user-select: none;
cursor: move;
}
:deep(.el-col) {
&.sortable-chosen > .ele-card {
box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.2);
}
&.sortable-ghost {
opacity: 0;
}
&.sortable-fallback {
opacity: 1 !important;
}
}
}
/* 底部按钮 */
.workplace-bottom {
display: flex;
align-items: center;
.workplace-button {
flex: 1;
padding: 10px 0;
transition: background-color 0.2s;
&:hover {
background: hsla(0, 0%, 60%, 0.05);
}
:deep(.el-icon) {
font-size: 15px;
margin: -1px 6px 0 0;
}
}
}
</style>
@@ -0,0 +1,92 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<el-form :inline="true" :model="pageForm">
<el-form-item label="工作室名称">
<el-input v-model="pageForm.name" clearable></el-input>
</el-form-item>
<el-form-item label="创建年份">
<el-date-picker v-model="pageForm.createYear" type="year" value-format="yyyy"></el-date-picker>
</el-form-item>
<el-form-item label="审核状态">
<el-select v-model="pageForm.processStatus" clearable>
<el-option label="待审核" :value="1"></el-option>
<el-option label="已审核" :value="5"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="pageData">查询</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" border>
<el-table-column type="index" label="序号" width="60"></el-table-column>
<el-table-column prop="name" label="工作室名称" min-width="160"></el-table-column>
<el-table-column prop="createYear" label="创立年度" width="100"></el-table-column>
<el-table-column prop="leadPeople" label="领衔人" width="120"></el-table-column>
<el-table-column prop="deptName" label="所属机构" min-width="140"></el-table-column>
<el-table-column prop="processTaskNode" label="审核节点" width="140"></el-table-column>
<el-table-column prop="processTaskReason" label="任务状态" width="100"></el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="{row}">
<el-button type="text" @click="audit(row, 'PASS')" v-if="row.processTaskReason==='ACTIVE'">通过</el-button>
<el-button type="text" @click="audit(row, 'REJECT')" v-if="row.processTaskReason==='ACTIVE'">驳回</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
pageForm: {
page: 1,
limit: 20,
processStatus: 1,
createYear: String(new Date().getFullYear())
},
tableData: []
}
},
methods: {
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
pageData() {
this.$axios.get('/platform/zhgh/brand/application/auditPage', {params: this.pageForm}).then((res) => {
const data = this.getData(res) || {}
this.tableData = data.list || data.data || []
})
},
audit(row, type) {
this.$prompt('请输入审批意见', '审批', {
inputType: 'textarea',
inputValue: type === 'PASS' ? '同意' : ''
}).then(({value}) => {
this.$axios.post('/platform/zhgh/brand/flow/audit', {
applicationId: row.id,
processInstanceId: row.processInstanceId,
processInstanceTaskId: row.processInstanceTaskId || row.flowId,
bpmTaskApprovalType: type,
remark: value
}).then(() => {
this.$message.success('操作成功')
this.pageData()
})
})
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,270 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<param-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<div class="radio-right">
<el-radio-group
v-model="auditStatus"
size="default"
@change="handleChange"
>
<el-radio-button
v-for="item in auditStatusOptions"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</el-radio-button>
</el-radio-group>
</div>
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
highlight-current-row
:export-config="{ fileName: '参数设置' }"
cache-key="systemConfigTable"
:tools="false"
>
<template #createTime="{ row }">
{{ dayjs(row.createTime).format('YYYY-MM-DD') }}
</template>
<template #leadIdentity="{ row }">
<div
style="display: flex"
v-for="(item, index) in JSON.parse(row.leadIdentity)"
:key="index"
>
<el-tag type="primary">
{{ item.dictLabel }}
</el-tag>
</div>
</template>
<template #memberNum="{ row }">
{{ row.memberNum + '人' }}
</template>
<template #status="{ row }">
<el-tag
style="cursor: pointer"
v-if="row.processUserTaskStatus === 1"
type="warning"
>
待审核
</el-tag>
<el-tag style="cursor: pointer" v-if="row.processUserTaskStatus === 3" type="danger">
拒绝
</el-tag>
<el-tag style="cursor: pointer" v-if="row.processUserTaskStatus === 4" type="info">
取消
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.processUserTaskStatus === 2"
type="success"
>
通过
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.processUserTaskStatus === 5"
type="danger"
>
已退回
</el-tag>
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click.stop="handleProcessDetail(row)"
:icon="row.processUserTaskStatus === 1 ? FileOutlined : View"
>
{{ row.processUserTaskStatus === 1 ? '审批' : '查看' }}
</el-link>
</template>
</ele-pro-table>
</ele-card>
</ele-page>
</template>
<script setup>
import { computed, ref } from 'vue';
import ParamSearch from '../components/param-search.vue';
import dayjs from 'dayjs';
import { getBrandApplicationAuditPageApi } from '@/api/office/brand/index.js';
import { FileOutlined } from '@/components/icons/index.js';
import {View} from "@element-plus/icons-vue";
import $enums from "@/utils/enums.js";
import {useRouter} from "vue-router";
defineOptions({ name: 'Review' });
const { push } = useRouter();
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => {
return [
{
type: 'index',
columnKey: 'index',
width: 60,
align: 'center',
label: '序号'
},
{
prop: 'name',
label: '工作室名称',
align: 'center',
minWidth: 180
},
{
prop: 'createYear',
label: '创立年度',
align: 'center',
minWidth: 80,
slot: 'createYear'
},
{
prop: 'leadPeople',
label: '领衔人',
minWidth: 100,
align: 'center',
slot: 'leadPeople'
},
{
prop: 'deptName',
label: '所属单位机构',
minWidth: 140,
align: 'center'
},
{
prop: 'type',
label: '工作室类型',
align: 'center',
minWidth: 110,
slot: 'type'
},
{
prop: 'track',
label: '工作室赛道',
align: 'center',
minWidth: 110,
slot: 'track'
},
{
prop: 'memberNum',
label: '成员人数',
align: 'center',
minWidth: 80,
slot: 'memberNum'
},
{
prop: 'address',
label: '工作室地址',
minWidth: 140,
align: 'center',
slot: 'address'
},
{
prop: 'createTime',
label: '申请时间',
minWidth: 100,
align: 'center',
slot: 'createTime'
},
{
prop: 'status',
label: '状态',
minWidth: 90,
align: 'center',
slot: 'status'
},
{
prop: 'processTaskNode',
label: '审核节点',
minWidth: 150,
align: 'center'
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
];
});
/** 表格选中数据 */
const selections = ref([]);
const auditStatus = ref(1);
const auditStatusOptions = [
{ label: '全部', value: 0 },
{ label: '未审核', value: 1 },
{ label: '已审核', value: 2 },
];
const handleChange = (val) => {
console.log('当前选中值:', val);
auditStatus.value = val;
if (val === 0) {
reload({});
} else {
reload({ processStatus: val });
}
};
/** 表格数据源 */
const datasource = async ({ pages, where, filters }) => {
const finalWhere = { ...where, processStatus: auditStatus.value, createYear: new Date().getFullYear().toString() };
const { data } = await getBrandApplicationAuditPageApi({
...finalWhere,
...filters,
...pages
});
return data;
};
/** 搜索 */
const reload = (where) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 审批进度 */
const handleProcessDetail = (row) => {
push({
name: 'WorkflowProcessInstanceDetail',
query: {
businessId: row.id,
businessType: $enums.BUSINESS_PROCESS_KEY_NUMBER.BRAND_OFFICE_USER_APPLY,
processInstanceId: row.processInstanceId,
processFormPath: encodeURI(
'/brand/components/flow-view.vue'
)
}
});
};
</script>
<style scoped>
.radio-right {
display: flex;
justify-content: flex-end; /* 关键:右对齐 */
}
</style>
@@ -0,0 +1,68 @@
<!-- 搜索表单 -->
<template>
<ele-card :body-style="{ paddingBottom: '2px' }">
<el-form label-width="72px" @keyup.enter="search" @submit.prevent="">
<el-row :gutter="8">
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="工作室名称" label-width="100px">
<el-input
clearable
v-model.trim="form.name"
placeholder="请输入工作室名称"
/>
</el-form-item>
</el-col>
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="创立年度">
<el-date-picker
clearable
v-model="form.createYear"
type="year"
value-format="YYYY"
placeholder="请选择工作室创立年度"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="工作室领衔人" label-width="100px">
<el-input
clearable
v-model.trim="form.leadPeople"
placeholder="请输入工作室领衔人"
/>
</el-form-item>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xs="24">
<el-form-item label-width="20px">
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</ele-card>
</template>
<script setup>
import { useFormData } from '@/utils/use-form-data.js';
const emit = defineEmits(['search']);
/** 表单数据 */
const [form, resetFields] = useFormData({
name: '',
leadPeople: '',
createYear: new Date().getFullYear().toString()
});
/** 搜索 */
const search = () => {
emit('search', { ...form, params: {} });
};
/** 重置 */
const reset = () => {
resetFields();
search();
};
</script>
@@ -0,0 +1,65 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<el-form :inline="true" :model="pageForm">
<el-form-item label="工作室名称">
<el-input v-model="pageForm.name" clearable></el-input>
</el-form-item>
<el-form-item label="创建年份">
<el-date-picker v-model="pageForm.createYear" type="year" value-format="yyyy"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="pageData">查询</el-button>
</el-form-item>
</el-form>
<el-table :data="tableData" border>
<el-table-column type="index" label="序号" width="60"></el-table-column>
<el-table-column prop="deptName" label="一级机构" min-width="120"></el-table-column>
<el-table-column prop="name" label="工作室名称" min-width="160"></el-table-column>
<el-table-column prop="leadPeople" label="领衔人" width="120"></el-table-column>
<el-table-column prop="honorName" label="外部荣誉称号" min-width="150"></el-table-column>
<el-table-column prop="createYear" label="创立年度" width="100"></el-table-column>
<el-table-column prop="track" label="所属领域" width="120"></el-table-column>
<el-table-column prop="leadHonor" label="领衔人荣誉称号" min-width="160"></el-table-column>
<el-table-column prop="memberNum" label="成员人数" width="90"></el-table-column>
<el-table-column prop="address" label="工作室地址" min-width="160"></el-table-column>
</el-table>
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
pageForm: {
page: 1,
limit: 20,
status: 5
},
tableData: []
}
},
methods: {
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
pageData() {
this.$axios.get('/platform/zhgh/brand/application/summaryPage', {params: this.pageForm}).then((res) => {
const data = this.getData(res) || {}
this.tableData = data.list || data.data || []
})
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,338 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<param-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
highlight-current-row
:export-config="exportConfig"
cache-key="systemConfigTable"
:tools="['export', 'print', 'columns']"
>
<template #createTime="{ row }">
{{ dayjs(row.createTime).format('YYYY-MM-DD') }}
</template>
<template #leadIdentity="{ row }">
<div
style="display: flex"
v-for="(item, index) in JSON.parse(row.leadIdentity)"
:key="index"
>
<el-tag type="primary">
{{ item.dictLabel }}
</el-tag>
</div>
</template>
<template #memberNum="{ row }">
{{ row.memberNum + '人' }}
</template>
<template #createYear="{ row }">
{{ row.createYear }}
</template>
<template #honorName="{ row }">
{{ row.honorName || '暂无' }}
</template>
<!-- <template #honorGrade="{ row }">-->
<!-- {{ row.honorGrade || '暂无' }}-->
<!-- </template>-->
<template #action="{ row }">
<el-link
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="openLook(row)"
:icon="FileOutlined"
>
查看
</el-link>
</template>
</ele-pro-table>
</ele-card>
</ele-page>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from 'vue';
import {EleMessage} from 'ele-admin-plus';
import ParamSearch from './components/param-search.vue';
import dayjs from 'dayjs';
import {
getBrandStudioApplicationHonorPageApi,
getUserBelongUnionApi
} from '@/api/office/brand/index.js';
import { FileOutlined } from '@/components/icons/index.js';
import { BRAND_EDIT_PATH } from '@/config/setting.js';
import { useRouter } from 'vue-router';
const { push } = useRouter();
import { usePageTab } from '@/utils/use-page-tab';
import request from '@/utils/request.js';
import { download } from '@/utils/common.js';
const { addPageTab } = usePageTab();
defineOptions({ name: 'BrandSummary' });
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => {
return [
// {
// type: 'selection',
// columnKey: 'selection',
// width: 50,
// align: 'center'
// },
{
type: 'index',
columnKey: 'index',
width: 60,
align: 'center',
label: '序号'
},
{
prop: 'name',
label: '工作室名称',
align: 'center',
minWidth: 180
},
{
prop: 'createYear',
label: '创立年度',
align: 'center',
minWidth: 80,
slot: 'createYear'
},
{
prop: 'leadPeople',
label: '领衔人',
minWidth: 100,
align: 'center',
slot: 'leadPeople'
},
{
prop: 'deptName',
label: '一级机构',
minWidth: 120,
align: 'center'
},
{
prop: 'honorName',
label: '外部荣誉称号',
minWidth: 150,
align: 'center',
slot: 'honorName'
},
// {
// prop: 'honorGrade',
// label: '级别',
// minWidth: 80,
// align: 'center',
// slot: 'honorGrade'
// },
{
prop: 'track',
label: '所属领域',
align: 'center',
minWidth: 110,
slot: 'track'
},
{
prop: 'leadHonor',
label: '领衔人荣誉称号',
align: 'center',
minWidth: 110,
slot: 'leadHonor'
},
{
prop: 'memberNum',
label: '成员人数',
align: 'center',
minWidth: 80,
slot: 'memberNum'
},
{
prop: 'address',
label: '工作室地址',
minWidth: 140,
align: 'center',
slot: 'address'
},
// {
// prop: 'remark',
// label: '备注',
// minWidth: 140,
// align: 'center'
// }
{
columnKey: 'action',
label: '操作',
width: 100,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
];
});
/** 表格选中数据 */
const selections = ref([]);
/** 表格数据源 */
const datasource = async ({ pages, where, filters }) => {
where = {
...where,
status: 5
};
const { data } = await getBrandStudioApplicationHonorPageApi({
...where,
...filters,
...pages
});
return data;
};
const unionName = ref('');
onMounted(async () => {
console.log('mounted');
const { data } = await getUserBelongUnionApi();
unionName.value = data.name;
});
/** 搜索 */
const reload = (where) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 打开编辑弹窗 */
const openLook = (row) => {
const path = BRAND_EDIT_PATH + '/' + row.id + '/look';
addPageTab({
title: '工作室管理',
key: path,
closable: true,
meta: { icon: 'LinkOutlined' }
});
push(path);
};
const exportUsers = async (data) => {
const res = await request({
url: '/act/brand/application/export',
method: 'POST',
data,
responseType: 'blob'
});
console.log('OK res=', res);
download(
res.data,
'全行品牌工作室清单.xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
};
import {excelExport} from "@/utils/export-config.js";
const exportConfig = reactive({
beforeExport: (params) => {
console.log("beforeExport")
// 导出为excel格式
excelExport(params, 'Sheet1');
// 禁用掉原始默认导出
return false;
},
// 数据源方法加 async 然后 return null
datasource: async ({ where, filters, pages, orders }) => {
console.log(columns.value);
// 这里就可以调用后端接口下载文件, 还可以传递表格当前的搜索、排序参数等
const loading = EleMessage.loading({
message: '正在下载文件..',
plain: true
});
exportUsers({ ...where, ...orders })
.then(() => {
loading.close();
})
.catch((e) => {
loading.close();
EleMessage.error(e.message);
});
},
// 例如设置导出默认的文件名
fileName: '全行品牌工作室清单'
});
// 模板导出
// import { utils, writeFile } from 'xlsx';
/* 导出excel */
const onExport = async () => {
const { data } = await getBrandStudioApplicationHonorPageApi({
page: 1,
limit: 1000
});
console.log(data);
const array = [
[
'序号',
'一级机构',
'工作室名称',
'领衔人',
'外部荣誉称号',
'级别',
'创立/获评年度',
'所属领域',
'是否有总行先进领衔(领衔人荣誉称号)',
'是否有创新团队(工作室成员人数)',
'备注'
]
];
data.forEach((d, i) => {
array.push([
i + 1,
d.deptName,
d.name,
d.leadPeople,
d.honorName,
d.honorGrade,
d.createYear + '/' + d.requireYear,
d.track,
d.leadHonor,
d.memberNum,
d.remark
]);
});
console.log(data);
// const sheet = utils.aoa_to_sheet(array);
// // 如果需要设置列宽可以这样写
// sheet['!cols'] = [
// { wch: 10 },
// { wch: 60 },
// { wch: 20 },
// { wch: 20 },
// { wch: 20 },
// { wch: 20 },
// { wch: 20 }
// ];
// writeFile(
// {
// SheetNames: ['Sheet1'],
// Sheets: { Sheet1: sheet }
// },
// '用户数据.xlsx'
// );
};
</script>
@@ -0,0 +1,998 @@
<script setup>
import { EleMessage } from 'ele-admin-plus';
import { computed, onMounted, reactive, ref, watch } from 'vue';
import {
ClusterOutlined,
DeleteOutlined,
UserOutlined,
AppstoreOutlined
} from '@/components/icons/index.js';
import { useFormData } from '@/utils/use-form-data.js';
import { useDictData } from '@/utils/use-dict-data.js';
import {
getBrandStudioApplicationApi,
getLeaderHonorApi,
getUserBelongUnionApi,
updateBrandStudioBaseInfoApi,
updateBrandStudioMemberOrHonorApi
} from '@/api/office/brand/index.js';
import { useRoute, useRouter } from 'vue-router';
import { QuestionFilled } from '@element-plus/icons-vue';
import UnionUserSelect from "@/components/UnionUserSelect/index.vue";
import {withRequiredHeader} from "@/views/brand/js/common-brand.js";
import {ElMessageBox} from "element-plus";
import {StrUtil} from "@/utils/toolkit.js";
const router = useRouter();
const route = useRoute();
const activeTab = ref('basic'); // 默认选中
const tabsOptions = ref([
{ name: 'basic', label: '基本信息', value: 'basic', index: 1 },
{ name: 'member', label: '成员信息', value: 'member', index: 2 },
{ name: 'honor', label: '荣誉信息', value: 'honor', index: 3 }
]);
/** 二级弹窗是否打开:选择用户 */
const selectUserVisible = ref(false);
/** 是否是修改:针对整体 */
const isUpdate = ref(false);
/** 是否可以编辑:针对申请表部分,主要是为了区分成员部分 */
const editMember = ref(true);
/** 提交状态 */
const loading = ref(false);
/** 领衔人荣誉 */
const leaderHonors = ref([]);
/** 表单实例 */
const formRef = ref();
/** 字典数据 */
const [
leadPeopleDict,
brandOfficeTypeDict,
userEducationDict,
brandOfficeDomainDict
] = useDictData([
'lead_people',
'brand_office_type',
'user_education',
'brand_office_domain'
]);
/** 表单数据 */
const [form, resetFields, assignFields] = useFormData({
id: '',
unionName: '', // 所属单位工会
deptName: '',
name: '', // 工作室名称
createYear: '', // 创建年度
leadPeople: '', // 工作室领衔人
leadAge: 25, // 领衔人年龄
leadIdentity: [], // 领衔人身份
memberNum: 0, // 成员人数
type: '', // 工作室类型
track: '', // 工作室赛道
address: '', // 工作室具体位置
introduction: '' // 工作室基本情况介绍
});
/** 表单验证规则 */
const rules = reactive({
name: [
{
required: true,
message: '请输入工作室名称',
type: 'string',
trigger: 'blur'
}
],
unionName: [
{
required: true,
message: '请输入所属单位工会',
type: 'string',
trigger: 'blur'
}
],
deptName: [
{
required: true,
message: '请输入所属单位机构',
type: 'string',
trigger: 'blur'
}
],
createYear: [
{
required: true,
message: '请选择创建年度',
type: 'string',
trigger: 'change'
}
],
leadPeople: [
{
required: true,
message: '请选择工作室领衔人',
type: 'string',
trigger: 'blur'
},
{ type: 'string', max: 20, message: '领衔人最多 20 个字符', trigger: ['blur', 'change'] }
],
leadAge: [
{
required: true,
message: '请输入领衔人年龄',
type: 'number',
trigger: 'blur'
},
{
type: 'number',
min: 0,
max: 150,
message: '年龄必须在0-150岁之间',
trigger: 'blur'
}
],
leadIdentity: [
{
required: true,
message: '请选择领衔人身份',
type: 'array',
trigger: 'change'
}
],
leadHonor: [
{
required: true,
message: '请输入领衔人荣誉称号',
type: 'string',
trigger: 'change'
}
],
memberNum: [
{
required: true,
message: '请输入成员人数',
type: 'number',
trigger: 'blur'
}
],
type: [
{
required: true,
message: '请选择工作室类型',
type: 'string',
trigger: 'change'
}
],
track: [
{
required: true,
message: '请输入工作室赛道',
type: 'string',
trigger: 'blur'
}
],
address: [
{
required: true,
message: '请输入工作室具体位置',
type: 'string',
trigger: 'blur'
}
],
introduction: [
{
required: true,
message: '请填写工作室基本情况介绍',
type: 'string',
trigger: 'blur'
}
]
});
const validateMembers = () => {
const members = editableData.value;
// 1. 至少 1 人
if (!Array.isArray(members) || members.length < 1) {
ElMessageBox.alert(
'请至少选择 1 位工作室成员',
'系统提示',
{ type: 'warning' }
);
return false;
}
// 2. 必填字段校验
const requiredFields = columns.value
.filter(col => col.notRequired !== true)
.map(col => col.prop);
for (let i = 0; i < members.length; i++) {
const member = members[i];
for (const field of requiredFields) {
if (StrUtil.isEmpty(member[field])) {
const label = fieldLabelMap[field] || field;
ElMessageBox.alert(
`${member.name || '未命名'}】的【${label}】不能为空`,
'系统提示',
{ type: 'warning' }
);
return false;
}
}
}
return true;
};
const validateHonors = () => {
const honors = editableHonorData.value;
// 1. 至少一条
if (!Array.isArray(honors) || honors.length < 1) {
ElMessageBox.alert(
'请至少填写一条工作室荣誉',
'系统提示',
{ type: 'warning' }
);
return false;
}
// 2. 必填字段校验
const requiredFields = [
{ field: 'honorName', label: '荣誉称号' },
{ field: 'requireYear', label: '获取年度' },
{ field: 'honorCategory', label: '荣誉类别' }
];
for (let i = 0; i < honors.length; i++) {
const item = honors[i];
for (const { field, label } of requiredFields) {
if (StrUtil.isEmpty(item[field])) {
ElMessageBox.alert(
`${i + 1} 条荣誉的【${label}】不能为空`,
'系统提示',
{ type: 'warning' }
);
return false;
}
}
}
return true;
};
/** 返回 */
const goBack = () => {
router.back();
};
const showSelectUser = () => {
selectUserVisible.value = true;
};
// 可选择的成员
const userIds = ref([]);
const topUnionId = ref('');
const unionId = ref('');
/** 选择成员 */
// user-select 组件回调
const handleSelect = (data) => {
// orgEditableData 原来数据
const orgEditableData = editableData.value;
// 查找已经存在的用户
const existingIds = new Set(
orgEditableData.map((item) => item.userId || item.id)
);
// 过滤掉已经选择的用户,之前有的就不做处理
const selectData = data
.filter((item) => !existingIds.has(item.id))
.map((item) => ({
...item,
userId: item.id,
oldName: item.name,
oldBirthdate: item.birthdate
}));
editableData.value = [...orgEditableData, ...selectData];
form.memberNum = editableData.value.length;
// 选完情况组件中的用户
userIds.value = [];
};
/** beforeConfirm 为确定按钮点击钩子, 可以 return false 阻止确定 */
const beforeConfirm = (data) => {
if (!data?.length) {
EleMessage.error('请至少选择一个用户');
return false;
}
};
// 可编辑的副本(用于 v-model)
const editableData = ref([]);
const removeMember = (row) => {
if (!editMember.value) {
EleMessage.warning({ message: '查看界面,无法删除', plain: true });
return false;
}
const index = editableData.value.findIndex(
(item) => item.userId === row.userId
);
if (index !== -1) {
userIds.value.splice(index, 1);
editableData.value.splice(index, 1);
form.memberNum--;
}
};
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => [
withRequiredHeader({ prop: 'name', label: '姓名', align: 'center', minWidth: 80, slot: 'name' }),
withRequiredHeader({ prop: 'birthdate', label: '出生年月', align: 'center', minWidth: 120, slot: 'birthdate', required: true }),
withRequiredHeader({ prop: 'grade', label: '学历', align: 'center', minWidth: 80, slot: 'grade', required: true }),
withRequiredHeader({ prop: 'professional', label: '职称', align: 'center', minWidth: 100, slot: 'professional', required: true }),
withRequiredHeader({ prop: 'deptName', label: '所在部门', align: 'center', minWidth: 100, slot: 'deptName' }),
withRequiredHeader({ columnKey: 'action', label: '操作', width: 80, align: 'center', slot: 'action', notRequired: true })
]);
// 自动生成映射:{ name: '姓名', birthdate: '出生年月', ... }
const fieldLabelMap = {};
columns.value.forEach((col) => {
fieldLabelMap[col.prop] = col.label;
});
/** 表格实例 */
const honorTableRef = ref(null);
/** 表格列配置 */
const honorColumns = computed(() => {
return [
{
prop: 'honorName',
label: '荣誉称号',
align: 'center',
minWidth: 120,
slot: 'honorName'
},
{
prop: 'requireYear',
label: '获取年度',
align: 'center',
minWidth: 80,
slot: 'requireYear'
},
{
prop: 'honorCategory',
label: '荣誉类别',
align: 'center',
minWidth: 100,
slot: 'honorCategory'
},
{
columnKey: 'action',
label: '操作',
width: 80,
align: 'center',
slot: 'action',
notRequired: true
}
];
});
// 可编辑的副本(用于 v-model)
const editableHonorData = ref([]);
const addHonor = () => {
editableHonorData.value.push({
id: editableHonorData.value.length + 1,
honorName: '',
honorGrade: '',
requireYear: '',
honorCategory: 1
});
};
const removeHonor = (row) => {
console.log(row);
if (!editMember.value) {
EleMessage.warning({ message: '查看界面,无法删除', plain: true });
return false;
}
const index = editableHonorData.value.findIndex(
(item) => item.id === row.id
);
if (index !== -1) {
editableHonorData.value.splice(index, 1);
}
};
const save = (isBase) => {
loading.value = true;
console.log('save', isBase);
// 1 = 基本信息,2 = 成员,3 = 荣誉
if (isBase === 2) {
if (!validateMembers()) {
loading.value = false;
return;
}
}
if (isBase === 3) {
if (!validateHonors()) {
loading.value = false;
return;
}
}
formRef.value?.validate?.((valid) => {
if (!valid && isBase === 1) {
loading.value = false;
return;
}
const updateApi = isBase === 1
? updateBrandStudioBaseInfoApi
: updateBrandStudioMemberOrHonorApi;
const params = { ...form };
params.leadIdentity = JSON.stringify(form.leadIdentity) || {};
params.brandStudioMembers = form.unionUsers || [];
params.domain = params.track;
params.id = form.id;
params.memberNum = form.memberNum;
params.brandStudioMembers = editableData.value || [];
params.brandStudioHonors = editableHonorData.value || [];
updateApi(params)
.then(() => {
EleMessage.success({ message: '保存成功', plain: true });
queryInfoById(form.id);
})
.catch((err) => {
EleMessage.error({ message: '服务错误', plain: true });
console.warn(err);
})
.finally(() => {
loading.value = false;
});
});
};
const queryInfoById = (id) => {
userIds.value = [];
getBrandStudioApplicationApi(id)
.then(({ data }) => {
const formData = {
...data,
leadIdentity: JSON.parse(data.leadIdentity)
};
form.leadHonor = formData.leadHonor;
assignFields(formData);
editableData.value =
data.brandStudioMembers?.map((item) => {
// userIds.value.push(item.userId);
return {
...item,
oldName: item.name,
oldBirthdate: item.birthdate
};
}) || [];
console.log(data.brandStudioHonors);
editableHonorData.value = data.brandStudioHonors || [];
loadUserUnion()
})
.catch((err) => {
console.log(err);
});
};
const leaderChange = (emplid) => {
console.log('emplid: ' + emplid);
const matchedItem = leaderHonors.value.find(
(item) => item.emplid === emplid
);
if (matchedItem) {
form.leadHonor = matchedItem.honorNames?.join(',') || '';
form.leadAge = matchedItem.age || 0;
form.leadPeople = matchedItem.userName || '';
} else {
form.leadHonor = '';
form.leadAge = 0;
}
};
// 获取成员当前所在工会
const loadUserUnion = async () => {
const { data } = await getUserBelongUnionApi();
unionId.value = data.unionId;
topUnionId.value = data.topUnionId;
// form.unionName = data.unionName;
// form.deptName = data.deptName;
// form.createYear = new Date().getFullYear().toString();
}
onMounted(async () => {
console.log('mounted');
await loadUserUnion()
// 获取领衔人
const res = await getLeaderHonorApi();
leaderHonors.value = res.data.map((item, index) => {
return {
id: index,
emplid: item.emplid,
userName: item.userName,
age: item.age,
honorNames: item.honorNames
};
});
});
watch(
() => route.params.id,
() => {
editMember.value = route.params.edit === 'edit';
const id = route.params.id;
if (id) {
queryInfoById(id);
isUpdate.value = true;
} else {
resetFields();
editableData.value = [];
isUpdate.value = false;
// form.unionName = route.query.unionName;
}
},
{ immediate: true }
);
</script>
<template>
<ele-page>
<ele-card style="height: 800px">
<ele-tabs
v-model="activeTab"
:items="tabsOptions"
type="indicator"
style="margin-bottom: 8px"
>
<template #label="{ item }">
<div
class="tab-item-container"
style="display: flex; align-items: center; gap: 8px"
>
<ClusterOutlined
v-if="item.index === 1"
style="width: 14px; height: 14px"
/>
<UserOutlined
v-else-if="item.index === 2"
style="width: 14px; height: 14px"
/>
<AppstoreOutlined
v-else-if="item.index === 3"
style="width: 14px; height: 14px"
/>
<span>{{ item.label }}</span>
</div>
</template>
<template #basic>
<el-form
style="padding: 20px 20px"
ref="formRef"
:model="form"
:rules="rules"
label-width="110px"
@submit.prevent=""
:disabled="true"
>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="所属单位工会" prop="unionName" required>
<el-input
v-model="form.unionName"
:disabled="true"
placeholder="请输入所属单位工会"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属单位机构" prop="deptName" required>
<el-input
v-model="form.deptName"
:disabled="true"
placeholder="请输入所属单位机构"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室名称" prop="name" required>
<el-input
v-model="form.name"
placeholder="请输入工作室名称"
maxlength="30"
show-word-limit
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="创建年度" required prop="createYear">
<el-date-picker
clearable
v-model="form.createYear"
type="year"
value-format="YYYY"
placeholder="请选择年度"
:disabled="true"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室领衔人" prop="leadPeople" required>
<el-select
filterable
allow-create
default-first-option
placeholder="请选择工作室领衔人"
v-model="form.leadPeople"
@change="leaderChange"
value-key="id"
clearable
>
<el-option
v-for="item in leaderHonors"
:key="item.emplid"
:value="item.emplid"
:label="`${item.emplid} - ${item.userName}`"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人年龄" required prop="leadAge">
<el-input-number
placeholder="请输入领衔人年龄"
v-model="form.leadAge"
min="0"
max="150"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="领衔人身份" prop="leadIdentity">
<el-select
placeholder="请选择领衔人身份"
v-model="form.leadIdentity"
multiple
value-key="id"
clearable
>
<el-option
v-for="item in leadPeopleDict"
:key="item.id"
:value="{
id: item.id,
dictLabel: item.dictLabel,
dictTypeId: item.dictTypeId
}"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="荣誉称号" required prop="leadHonor">
<el-input
placeholder="请输入领衔人荣誉称号"
v-model="form.leadHonor"
maxlength="40"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室类型" prop="type">
<el-select
placeholder="请选择工作室类型"
v-model="form.type"
value-key="id"
clearable
>
<el-option
v-for="item in brandOfficeTypeDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属赛道" prop="track" required>
<el-select
placeholder="请选择工作室赛道"
v-model="form.track"
value-key="id"
clearable
>
<el-option
v-for="item in brandOfficeDomainDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="具体位置" prop="address" required>
<el-input
v-model="form.address"
placeholder="请输入工作室具体位置"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="基本情况介绍" required prop="introduction">
<el-input
:rows="8"
type="textarea"
v-model="form.introduction"
placeholder="请输入基本情况介绍"
clearable
maxlength="1500"
show-word-limit
/>
</el-form-item>
<div
style="flex: 1; display: flex; justify-content: flex-end"
v-if="editMember"
>
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="save(1)">
保存信息
</el-button>
</div>
</el-form>
</template>
<template #member>
<div style="padding: 20px 20px">
<div style="display: flex; justify-content: space-between">
<el-form-item
label="工作室成员"
required
prop="unionUsers"
:disabled="!editMember"
>
<span>{{ form.memberNum }}</span>
<el-tooltip
content="团队成员数量,根据选择成员人数自动变化"
placement="top"
>
<el-icon
style="cursor: help; margin-left: 4px; color: #909399"
>
<QuestionFilled />
</el-icon>
</el-tooltip>
</el-form-item>
<div v-if="editMember">
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="showSelectUser">
选择工作室成员
</el-button>
<el-button type="primary" @click="save(2)">
保存人员
</el-button>
</div>
</div>
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:height="500"
:columns="columns"
:datasource="editableData"
:show-overflow-tooltip="true"
highlight-current-row
:export-config="{ fileName: '用户数据' }"
:style="{ paddingBottom: '16px' }"
cache-key="systemUserTable"
:tools="false"
>
<template #name="{ row }">
<span v-if="editMember && false">
<el-input class="ipt-text" v-model="row.name" maxlength="20" show-word-limit />
</span>
<span v-else>{{ row.name }}</span>
</template>
<template #birthdate="{ row }">
<span v-if="editMember">
<el-date-picker
style="width: 160px; padding: 0 20px"
v-model="row.birthdate"
type="month"
placeholder="选择年月"
format="YYYY-MM"
value-format="YYYY-MM"
size="large"
/>
</span>
<span v-else>{{ row.birthdate }}</span>
</template>
<template #grade="{ row }">
<span class="ipt-text">
<el-select
:disabled="!editMember"
placeholder="请选择学历类型"
v-model="row.grade"
value-key="id"
clearable
maxlength="30"
show-word-limit
>
<el-option
v-for="item in userEducationDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</span>
</template>
<template #professional="{ row }">
<span v-if="editMember" class="ipt-text">
<el-input v-model="row.professional" maxlength="30" show-word-limit />
</span>
<span v-else>{{ row.professional }}</span>
</template>
<template #deptName="{ row }">
<span class="ipt-text" v-if="editMember && false">
<el-input v-model="row.deptName" />
</span>
<span v-else>{{ row.deptName }}</span>
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:remove'"
type="danger"
underline="never"
@click="removeMember(row)"
:icon="DeleteOutlined"
:disabled="!editMember"
>
删除
</el-link>
</template>
</ele-pro-table>
</div>
</template>
<template #honor>
<div style="padding: 20px 20px">
<div style="display: flex; justify-content: space-between">
<el-form-item label="工作室荣誉" required prop="honors" />
<div v-if="editMember">
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="addHonor">
添加荣誉
</el-button>
<el-button type="primary" @click="save(3)">
保存荣誉
</el-button>
</div>
</div>
<!-- 表格 -->
<ele-pro-table
ref="honorTableRef"
row-key="id"
:height="500"
:columns="honorColumns"
:datasource="editableHonorData"
:show-overflow-tooltip="true"
highlight-current-row
:style="{ paddingBottom: '16px' }"
cache-key="systemUserTable"
:tools="false"
>
<template #honorName="{ row }">
<span class="ipt-text">
<el-input
v-if="editMember"
v-model="row.honorName"
maxlength="30" show-word-limit
/>
<span v-else>{{ row.honorName }}</span>
</span>
</template>
<template #requireYear="{ row }">
<span v-if="editMember" class="ipt-text">
<el-date-picker
style="padding: 0 20px"
v-model="row.requireYear"
type="year"
placeholder="选择年度"
format="YYYY"
value-format="YYYY"
size="large"
/>
</span>
<span v-else>{{ row.requireYear }}</span>
</template>
<template #honorCategory="{ row }">
<span class="ipt-text">
<el-select
:disabled="!editMember"
style="padding: 0 20px"
placeholder="请选择荣誉类别"
v-model="row.honorCategory"
value-key="id"
clearable
>
<el-option
v-for="item in [
{
id: 1,
label: '外部荣誉'
},
{
id: 2,
label: '行内荣誉'
}
]"
:key="item.id"
:value="item.id"
:label="item.label"
/>
</el-select>
</span>
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:remove'"
type="danger"
underline="never"
@click="removeHonor(row)"
:icon="DeleteOutlined"
:disabled="!editMember"
>
删除
</el-link>
</template>
</ele-pro-table>
</div>
</template>
</ele-tabs>
</ele-card>
<union-user-select
clearable
multiple
view-type="picker"
v-model:visible="selectUserVisible"
v-model="userIds"
placeholder="请选择用户"
queryType="union"
:union-id="topUnionId"
@select="handleSelect"
:before-confirm="beforeConfirm"
/>
</ele-page>
</template>
<style scoped>
.ipt-text {
:deep(.el-input__inner) {
text-align: center;
}
:deep(.el-select__selected-item) {
text-align: center;
}
:deep(.el-date-picker__input) {
text-align: center;
}
}
.foot-btn {
display: flex;
justify-content: flex-end;
}
</style>