Merge remote-tracking branch 'refs/remotes/origin/feature_品牌工作室' into release_20260829

This commit is contained in:
2026-08-29 08:52:42 +08:00
85 changed files with 12236 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,140 @@
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.ArrayList;
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("/members")
public Result listMemberCandidates(String unionId, String keyword) {
return Result.success(applicationService.listMemberCandidates(unionId, keyword));
}
@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) {
List<String> ids = new ArrayList<>();
Object data = request.get("data");
if (data instanceof List<?>) {
((List<?>) data).forEach(item -> ids.add(String.valueOf(item)));
} else if (data != null) {
ids.add(String.valueOf(data));
}
Object id = request.get("id");
if (id != null) {
ids.add(String.valueOf(id));
}
request.forEach((key, value) -> {
if (key != null && String.valueOf(key).startsWith("data[") && value != null) {
ids.add(String.valueOf(value));
}
});
applicationService.delete(ids);
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,62 @@
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 Integer submitType;
/**
* 下一个任务接收人
*/
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,154 @@
package com.budwk.app.zhgh.brand.domain.dto;
import lombok.Data;
import org.nutz.json.Json;
import org.nutz.lang.Strings;
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;
/**
* 工作室成员列表 JSON
*/
private String brandStudioMembersJson;
public void setBrandStudioMembersJson(String brandStudioMembersJson) {
this.brandStudioMembersJson = brandStudioMembersJson;
if (Strings.isNotBlank(brandStudioMembersJson)) {
this.brandStudioMembers = Json.fromJsonAsList(BrandStudioMemberCreateDTO.class, brandStudioMembersJson);
}
}
}
@@ -0,0 +1,154 @@
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;
/**
* 是否已审核
*/
private Boolean audit = false;
@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,143 @@
package com.budwk.app.zhgh.brand.domain.dto;
import lombok.Data;
import org.nutz.json.Json;
import org.nutz.lang.Strings;
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;
/**
* 发起任务ID
*/
private String startTaskId;
/**
* 归档
*/
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;
/**
* 添加的成员 JSON
*/
private String brandStudioMembersJson;
public void setBrandStudioMembersJson(String brandStudioMembersJson) {
this.brandStudioMembersJson = brandStudioMembersJson;
if (Strings.isNotBlank(brandStudioMembersJson)) {
this.brandStudioMembers = Json.fromJsonAsList(BrandStudioMemberCreateDTO.class, brandStudioMembersJson);
}
}
private List<BrandStudioHonorCreateDTO> brandStudioHonors;
/**
* 添加的荣誉 JSON
*/
private String brandStudioHonorsJson;
public void setBrandStudioHonorsJson(String brandStudioHonorsJson) {
this.brandStudioHonorsJson = brandStudioHonorsJson;
if (Strings.isNotBlank(brandStudioHonorsJson)) {
this.brandStudioHonors = Json.fromJsonAsList(BrandStudioHonorCreateDTO.class, brandStudioHonorsJson);
}
}
}
@@ -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,202 @@
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;
/**
* 流程实例ID
*/
private Long instanceId;
/**
* 流程业务编号
*/
private String businessNo;
/**
* 流程状态
*/
private Integer instanceState;
/**
* 流程定义ID
*/
private String instanceProcessDefineId;
/**
* 当前任务ID
*/
private String taskId;
/**
* 当前任务标识
*/
private String taskKey;
/**
* 当前任务名称
*/
private String taskName;
/**
* 当前任务状态
*/
private Integer taskState;
/**
* 是否可以撤回
*/
private Boolean canRevoke;
/**
* 发起任务ID
*/
private String startTaskId;
/**
* 归档
*/
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,213 @@
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 Integer taskState;
/**
* 当前任务ID
*/
private String taskId;
/**
* 当前任务标识
*/
private String taskKey;
/**
* 当前任务名称
*/
private String taskName;
/**
* 当前节点
*/
private String curTaskName;
/**
* 审核时间
*/
private Date finishTime;
/**
* 是否可以撤回
*/
private Boolean canRevoke;
/**
* 流程状态
*/
private Integer instanceState;
/**
* 流程定义ID
*/
private String instanceProcessDefineId;
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,57 @@
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;
import org.nutz.lang.util.NutMap;
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();
List<NutMap> listMemberCandidates(String unionId, String keyword);
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,321 @@
package com.budwk.app.zhgh.brand.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
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.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.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 FlowCommonService flowCommonService;
@Inject
private FlowEngine flowEngine;
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不能为空");
}
ApplicationFlowStatusEnum statusEnum = getFlowStatusEnum(dto);
Dict args = Dict.create();
args.putAll(BeanUtil.beanToMap(dto));
args.set(FlowConst.PROCESS_TASK_ID_KEY, Long.valueOf(taskId));
args.set(FlowConst.APPROVAL_COMMENT, dto.getRemark());
args.set("tf_remark", dto.getRemark());
args.set(FlowConst.SUBMIT_TYPE, getProcessSubmitType(statusEnum));
flowCommonService.executeTask(args);
ProcessInstance instance = dao().fetch(ProcessInstance.class, Cnd.where("businessNo", "=", applicationId));
if (statusEnum == ApplicationFlowStatusEnum.REJECT) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.REJECT.getCode()),
Cnd.where("id", "=", applicationId));
} else if (statusEnum == ApplicationFlowStatusEnum.RECALL) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.DRAFT.getCode()),
Cnd.where("id", "=", applicationId));
} else if (instance != null && ProcessInstanceStateEnum.FINISHED.getCode().equals(instance.getState())) {
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.PASSED.getCode()),
Cnd.where("id", "=", applicationId));
}
}
private ApplicationFlowStatusEnum getFlowStatusEnum(BrandApplicationAuditDTO dto) {
if (ProcessSubmitTypeEnum.AGREE.getCode().equals(dto.getSubmitType())) {
return ApplicationFlowStatusEnum.PASSED;
}
if (ProcessSubmitTypeEnum.REJECT.getCode().equals(dto.getSubmitType())) {
return ApplicationFlowStatusEnum.REJECT;
}
if (ProcessSubmitTypeEnum.ROLLBACK_TO_OPERATOR.getCode().equals(dto.getSubmitType())) {
return ApplicationFlowStatusEnum.RECALL;
}
if (StrUtil.equalsIgnoreCase(dto.getBpmTaskApprovalType(), "PASS")) {
return ApplicationFlowStatusEnum.PASSED;
}
if (StrUtil.equalsIgnoreCase(dto.getBpmTaskApprovalType(), "REJECT")) {
return ApplicationFlowStatusEnum.REJECT;
}
if (StrUtil.equalsIgnoreCase(dto.getBpmTaskApprovalType(), "BACK")
|| StrUtil.equalsIgnoreCase(dto.getBpmTaskApprovalType(), "BACK_OTHER_NODE")) {
return ApplicationFlowStatusEnum.RECALL;
}
if (dto.getStatus() == null) {
throw new RuntimeException("任务审批类型错误,请传入正确的任务审批类型。");
}
ApplicationFlowStatusEnum statusEnum = ApplicationFlowStatusEnum.getStatusEnumByValue(dto.getStatus());
if (statusEnum == null) {
throw new RuntimeException("任务审批类型错误,请传入正确的任务审批类型。");
}
return statusEnum;
}
private Integer getProcessSubmitType(ApplicationFlowStatusEnum statusEnum) {
if (statusEnum == ApplicationFlowStatusEnum.PASSED) {
return ProcessSubmitTypeEnum.AGREE.getCode();
} else if (statusEnum == ApplicationFlowStatusEnum.REJECT) {
return ProcessSubmitTypeEnum.REJECT.getCode();
} else if (statusEnum == ApplicationFlowStatusEnum.RECALL) {
return ProcessSubmitTypeEnum.ROLLBACK_TO_OPERATOR.getCode();
}
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)) {
flowCommonService.revokeTask(Long.valueOf(taskId));
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.DRAFT.getCode()),
Cnd.where("id", "=", dto.getApplicationId()));
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(dto.getApplicationId());
return;
}
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.DRAFT.getCode()),
Cnd.where("id", "=", dto.getApplicationId()));
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(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,593 @@
package com.budwk.app.zhgh.brand.service.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import cn.hutool.core.lang.Dict;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.service.FlowCommonService;
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.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
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.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
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 FlowEngine flowEngine;
@Inject
private FlowCommonService flowCommonService;
public BrandStudioApplicationServiceImpl(Dao dao) {
super(dao);
}
// 分页查询申请表
@Override
public Pagination<BrandStudioApplicationVO> page(BrandStudioApplicationPageDTO pageDTO) {
String userId = SecurityUtil.getUserId();
Sql sql = Sqls.create("""
select bsa.*,
ins.id as instanceId,
ins.businessNo,
ins.state as instanceState,
ins.processDefineId as instanceProcessDefineId,
task.id as taskId,
task.taskName as taskKey,
task.displayName as taskName,
task.taskState,
if((select taskName from wf_process_task tt where tt.id = task.taskParentId) = 'startTask', 1, 0) as canRevoke,
(select max(id) from wf_process_task where processInstanceId = ins.id and taskName = 'startTask' and taskState in (10,20)) as startTaskId
from brand_studio_application bsa
left join wf_process_instance ins on ins.businessNo = bsa.id
left join wf_process_task task on task.processInstanceId = ins.id and task.taskState = 10
$condition
""");
Cnd cnd = Cnd.where("bsa.delFlag", "=", false);
/// 2. 先写“普通筛选”这一整组
if (StrUtil.isNotBlank(pageDTO.getName())) {
cnd.and("bsa.name", "like", "%" + pageDTO.getName() + "%");
}
if (StrUtil.isNotBlank(userId)) {
cnd.and("bsa.createdBy", "=", userId);
}
if (StrUtil.isNotBlank(pageDTO.getCreateYear())) {
cnd.and("bsa.createYear", "=", pageDTO.getCreateYear());
}
if (pageDTO.getStatus() != null) {
cnd.and("bsa.status", "=", pageDTO.getStatus());
}
if (StrUtil.isNotBlank(pageDTO.getLeadPeople())) {
cnd.and("bsa.leadPeople", "like", "%" + pageDTO.getLeadPeople() + "%");
}
cnd.desc("bsa.createYear");
cnd.desc("bsa.createdAt");
sql.setCondition(cnd);
return listPageVO(pageDTO, sql, BrandStudioApplicationVO.class);
}
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("提交申请失败");
}
try {
Dict args = Dict.create();
args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.APPLY.getCode());
args.set(FlowConst.FORM_DATA, application);
args.set("applicationId", application.getId());
args.set("status", BrandApplicationStatusEnums.IN_REVIEW.getCode());
args.set("userId", user.getId());
// 启动老工作流流程实例,复用 wf_process_define 中的 BRAND_STUDIO 流程定义
ProcessInstance instance = flowEngine.startProcessInstanceByKey("BRAND_STUDIO", application.getId(), SecurityUtil.getUserId(), args);
// 自动完成第一个申请任务
List<ProcessTask> doingTaskList = flowEngine.processTaskService().getDoingTaskList(instance.getId(), null);
for (ProcessTask task : doingTaskList) {
flowEngine.executeProcessTask(task.getId(), SecurityUtil.getUserId(), args);
}
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) {
if (StrUtil.isNotBlank(dto.getStartTaskId())) {
submitAgain(fetch(dto.getId()), dto.getStartTaskId(), getCheckedUserDetail());
} else {
/// 提交审核的话会记录在审核流表中
submitAudit(fetch(dto.getId()), getCheckedUserDetail());
}
}
}
private void submitAgain(BrandStudioApplication application, String taskId, Sys_user user) {
if (application == null) {
throw new RuntimeException("提交申请失败");
}
Dict args = Dict.create();
args.set(FlowConst.PROCESS_TASK_ID_KEY, Long.valueOf(taskId));
args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode());
args.set(FlowConst.FORM_DATA, application);
args.set("applicationId", application.getId());
args.set("status", BrandApplicationStatusEnums.IN_REVIEW.getCode());
args.set("userId", user.getId());
flowCommonService.executeTask(args);
dao().update(BrandStudioApplication.class,
Chain.make("status", BrandApplicationStatusEnums.IN_REVIEW.getCode()),
Cnd.where("id", "=", application.getId()));
}
@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) {
if (ids == null || ids.isEmpty()) {
throw new RuntimeException("请选择要删除的数据");
}
String userId = SecurityUtil.getUserId();
SqlExpressionGroup userGroup = new SqlExpressionGroup();
userGroup.or("userId", "=", userId);
userGroup.or("createdBy", "=", userId);
List<String> validIds = dao().query(BrandStudioApplication.class, Cnd.where("id", "in", ids)
.and(userGroup)
.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);
validIds.forEach(id -> flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id));
}
@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)
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.toList());
return new LeaderHonorGroupVO(emplid, userName, age, honorNames);
})
.collect(Collectors.toList());
}
@Override
public List<NutMap> listMemberCandidates(String unionId, String keyword) {
Sql sql = Sqls.create("""
select id,
id as userId,
loginname as emplid,
loginname,
username as name,
username,
date_format(birthday, '%Y-%m') as birthdate,
academicDegree as grade,
professionalTitle as professional,
unitName as deptName,
unitName,
unionId
from vw_user
$condition
""");
Cnd cnd = Cnd.NEW();
boolean isAdmin = AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
if (isAdmin) {
cnd.andEX("unionId", "=", unionId);
} else {
cnd.and("unionId", "=", SecurityUtil.getUnionId());
}
if (StrUtil.isNotBlank(keyword)) {
SqlExpressionGroup group = new SqlExpressionGroup();
group.orLike("loginname", keyword, true);
group.orLike("username", keyword, true);
cnd.and(group);
}
cnd.asc("loginname");
sql.setCondition(cnd);
Pagination pagination = listPageMap(1, 50, sql);
return pagination.getList();
}
@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);
}
ProcessInstance instance = dao().fetch(ProcessInstance.class, Cnd.where("businessNo", "=", id).desc("createdAt"));
if (instance != null) {
applicationVO.setInstanceId(instance.getId());
applicationVO.setBusinessNo(instance.getBusinessNo());
applicationVO.setInstanceState(instance.getState());
applicationVO.setInstanceProcessDefineId(String.valueOf(instance.getProcessDefineId()));
ProcessTask startTask = dao().fetch(ProcessTask.class, Cnd.where("processInstanceId", "=", instance.getId())
.and("taskName", "=", "startTask")
.and("taskState", "=", ProcessTaskStateEnum.DOING.getCode())
.desc("id"));
if (startTask != null) {
applicationVO.setStartTaskId(String.valueOf(startTask.getId()));
}
}
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.state as instanceState,
inst.processDefineId as instanceProcessDefineId,
task.id as taskId, task.id as flowId, task.id as processInstanceTaskId,
task.taskName as taskKey, task.displayName as taskName, task.displayName as processTaskNode,
task.taskState, task.finishTime,
ifnull(group_concat(distinct nextTask.displayName), '结束') as curTaskName,
if(revokeTask.id is not null, 1, 0) as canRevoke,
case task.taskState
when 10 then 'ACTIVE'
when 20 then 'COMPLETE'
when 30 then 'WITHDRAW'
when 40 then 'INTERRUPT'
when 50 then 'PENDING'
when 60 then 'TRANSFER'
when 99 then 'ABANDON'
else cast(task.taskState as char)
end as processTaskReason,
from_unixtime(task.createdAt / 1000) as processTaskStartTime,
task.finishTime as processTaskEndTime
from wf_process_task task
left join wf_process_instance inst on inst.id = task.processInstanceId
left join brand_studio_application bsa on bsa.id = inst.businessNo
left join wf_process_task_actor taskActor on taskActor.processTaskId = task.id
left join wf_process_task nextTask on nextTask.processInstanceId = inst.id and nextTask.taskState = 10
left join wf_process_task revokeTask on revokeTask.processInstanceId = inst.id and revokeTask.taskParentId = task.id and revokeTask.taskState = 10
inner join wf_process_define def on inst.processDefineId = def.id and def.name = 'BRAND_STUDIO'
$condition
order by task.createdAt desc
""");
Cnd cnd = Cnd.where("bsa.delFlag", "=", 0);
cnd.and("taskActor.actorId", "in", List.of(SecurityUtil.getUserId()));
cnd.and("task.taskName", "!=", "startTask");
if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name(),
RoleConstant.SCHOOL_UNION_CHAIRMAN.name(), RoleConstant.SCHOOL_UNION_VICE_CHAIRMAN.name())) {
cnd.and("task.displayName", "like", "%校工会%");
} else if (AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_ADMIN.name(), RoleConstant.BRANCH_UNION_CHAIRMAN.name(),
RoleConstant.BRANCH_UNION_VICE_CHAIRMAN.name(), RoleConstant.BRANCH_UNION_OPERATOR.name())) {
cnd.and("task.displayName", "like", "%分工会%");
}
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 (Boolean.TRUE.equals(dto.getAudit())) {
cnd.and("task.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
} else {
cnd.and("task.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
cnd.and("bsa.status", "=", BrandApplicationStatusEnums.IN_REVIEW.getCode());
}
if (dto.getProcessStatus() != null) {
cnd.and("task.taskState", "=", getProcessTaskStatus(dto.getProcessStatus()));
}
if (dto.getStatus() != null) {
cnd.and("bsa.status", "=", dto.getStatus());
}
cnd.groupBy("task.id");
sql.setCondition(cnd);
return listPageVO(dto, sql, BrandStudioApplyFlowVO.class);
}
private Integer getProcessTaskStatus(Integer processStatus) {
if (Objects.equals(processStatus, 1)) {
return ProcessTaskStateEnum.DOING.getCode();
}
if (Objects.equals(processStatus, 5)) {
return ProcessTaskStateEnum.FINISHED.getCode();
}
return processStatus;
}
@Override
public Pagination<BrandStudioApplicationHonorVO> summaryPage(BrandStudioApplicationPageDTO pageDTO) {
Sql sql = Sqls.create("""
select bsa.*, group_concat(bsh.honorName order by bsh.requireYear desc separator ',') as honorName
from brand_studio_application bsa
left join brand_studio_honor bsh on bsa.id = bsh.applicationId and bsh.delFlag = 0
$condition
group by bsa.id
order by bsa.createYear desc, bsa.createdAt desc
""");
Cnd cnd = Cnd.where("bsa.delFlag", "=", 0);
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 h.id,
u.loginname as emplid,
h.userName,
timestampdiff(year, u.birthday, curdate()) as age,
h.unitId as deptid,
h.unionId,
h.unitName as deptName,
year(h.grantDate) as year,
prize.name as honorName,
h.prize as honorCode
from honor h
left join vw_user u on h.userId = u.id
left join honor_basic_settings prize on prize.id = h.prize
left join honor_basic_settings type on type.id = h.honorType
where h.userId is not null
and prize.name is not null
and (type.queryTypeCode = 'HONOR_SINGLE' or type.name = '个人荣誉')
""");
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,235 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<el-form :inline="true" :model="pageForm">
<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-input v-model="pageForm.name" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="pageData">查询</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="申请列表">
<el-button size="small" type="primary" @click="openApply()">申请品牌工作室</el-button>
</table-tool>
<el-table :data="tableData" border style="width: 100%">
<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.instanceId" type="info">草稿</el-tag>
<enum-tag
v-else
:value="row.instanceState"
name="ProcessInstanceStateEnum"
label_key="message"
size="small">
</enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="260" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)" v-if="row.instanceId">查看</el-button>
<el-button size="mini" type="primary" @click="openApply(row.id)" v-if="row.taskKey === 'startTask' || !row.instanceId">编辑</el-button>
<el-button size="mini" type="danger" @click="recallApply(row)" v-if="row.canRevoke === true || row.canRevoke === 1">撤回</el-button>
<el-button size="mini" type="danger" @click="deleteApply(row.id)" v-if="row.taskKey === 'startTask' || !row.instanceId">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<template #view>
<div>
<el-tabs tab-position="top" v-model="activeName">
<el-tab-pane name="1" label="基础信息">
<div class="process-title">
品牌工作室申请
<el-link type="primary" @click="openChart">点击查看流程图</el-link>
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="工作室名称">{{detail.name}}</el-descriptions-item>
<el-descriptions-item label="创立年度">{{detail.createYear}}</el-descriptions-item>
<el-descriptions-item label="所属工会">{{detail.unionName}}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{detail.deptName}}</el-descriptions-item>
<el-descriptions-item label="领衔人">{{detail.leadPeople}}</el-descriptions-item>
<el-descriptions-item label="领衔人年龄">{{detail.leadAge}}</el-descriptions-item>
<el-descriptions-item label="领衔人身份">{{detail.leadIdentity}}</el-descriptions-item>
<el-descriptions-item label="领衔人荣誉">{{detail.leadHonor}}</el-descriptions-item>
<el-descriptions-item label="工作室类型">{{detail.type}}</el-descriptions-item>
<el-descriptions-item label="所属赛道">{{detail.track}}</el-descriptions-item>
<el-descriptions-item label="工作室地址" :span="2">{{detail.address}}</el-descriptions-item>
<el-descriptions-item label="工作室介绍" :span="2">{{detail.introduction}}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{detail.remark}}</el-descriptions-item>
</el-descriptions>
<snaker-chart ref="snakerChartRef"></snaker-chart>
</el-tab-pane>
<el-tab-pane name="2" label="成员信息">
<div class="process-title">成员信息</div>
<el-table :data="detail.brandStudioMembers || []" border size="small">
<el-table-column label="姓名" prop="name" width="120"></el-table-column>
<el-table-column label="出生年月" prop="birthdate" width="120"></el-table-column>
<el-table-column label="学位" prop="grade" show-overflow-tooltip></el-table-column>
<el-table-column label="职称" prop="professional" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="deptName" show-overflow-tooltip></el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane name="3" label="审核记录">
<template v-if="doneTasks.length > 0" v-for="task in doneTasks">
<div class="mt10">
<div class="process-title">{{ task.displayName }}</div>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id"
v-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">{{ task.ext.initiatorName }}({{task.ext.initiatorAccount}})</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" v-if="!task.ext.isFirstTaskNode">{{ task.taskFormData.opinion || task.taskFormData.remark || task.taskFormData.approvalComment }}</el-descriptions-item>
</el-descriptions>
</div>
</template>
<template v-else>
<el-empty description="暂无审核记录"></el-empty>
</template>
</el-tab-pane>
</el-tabs>
</div>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
pageForm: {
page: 1,
limit: 20,
createYear: String(new Date().getFullYear())
},
tableData: [],
detail: {},
currentRow: {},
activeName: "1",
doneTasks: []
}
},
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, mode) {
let url = '/platform/zhgh/brand/apply/index'
const params = []
if (id) {
params.push('id=' + id)
}
if (mode) {
params.push('mode=' + mode)
}
if (params.length) {
url += '?' + params.join('&')
}
window.location.href = url
},
openView(row) {
this.$refs.guava.view(() => {
this.currentRow = row
this.activeName = "1"
this.loadDetail(row.id)
this.getDoneTasks(row)
})
},
loadDetail(id) {
this.$axios.get('/platform/zhgh/brand/application/' + id).then((res) => {
this.detail = this.getData(res) || {}
this.detail.brandStudioMembers = this.detail.brandStudioMembers || []
})
},
getDoneTasks(row) {
this.$axios.post('/flow/common/doneTasks', {
instanceId: row.instanceId,
bizId: row.id
}).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
openChart() {
if (!this.currentRow.instanceId || !this.currentRow.instanceProcessDefineId) {
this.$message.warning('暂无流程图信息')
return
}
this.$refs.snakerChartRef.onOpenFull(this.currentRow.instanceProcessDefineId, this.currentRow.instanceId)
},
recallApply(row) {
this.$confirm('您确定要撤回吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$axios.post('/platform/zhgh/brand/flow/recall', {
applicationId: row.id,
processInstanceTaskId: row.startTaskId
}).then(() => {
this.$message.success('撤回成功')
this.pageData()
})
})
},
deleteApply(id) {
this.$confirm('您确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$axios.post('/platform/zhgh/brand/application/delete', {
data: [id]
}).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,431 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<snaker-start slot="header" label="品牌工作室申请" define_key="BRAND_STUDIO"></snaker-start>
<el-form ref="form" :model="form" label-width="130px" :disabled="isView">
<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-input v-model="form.createYear" disabled></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属工会">
<el-select v-model="form.unionId" filterable placeholder="请选择所属工会" style="width: 100%" @change="unionChange">
<el-option
v-for="item in unionOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所在单位">
<el-input v-model="form.deptName" disabled></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人">
<el-select v-model="leadPeopleEmplid" filterable placeholder="请选择领衔人" style="width: 100%" @change="leaderChange">
<el-option
v-for="item in leaderHonors"
:key="item.emplid"
:label="item.label"
:value="item.emplid">
</el-option>
</el-select>
</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-select v-model="form.leadIdentity" multiple filterable placeholder="请选择领衔人身份" style="width: 100%">
<el-option
v-for="item in leadPeopleDict"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
</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-select v-model="form.type" filterable placeholder="请选择工作室类型" style="width: 100%">
<el-option
v-for="item in brandOfficeTypeDict"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属赛道">
<el-select v-model="form.track" filterable placeholder="请选择所属赛道" style="width: 100%" @change="trackChange">
<el-option
v-for="item in brandOfficeTrackDict"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
</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>
<div class="brand-member-toolbar" v-if="!isView">
<div class="brand-member-count">已选择 {{form.brandStudioMembers.length}} 人</div>
<el-button size="small" type="primary" icon="el-icon-plus" @click="openMemberDialog">选择工作室成员</el-button>
</div>
<div class="brand-member-toolbar" v-else>
<div class="brand-member-count">已选择 {{form.brandStudioMembers.length}} 人</div>
</div>
<div class="brand-member-table">
<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" disabled></el-input>
</template>
</el-table-column>
<el-table-column label="出生年月" min-width="120">
<template slot-scope="{row}">
<el-date-picker
v-model="row.birthdate"
type="month"
value-format="yyyy-MM"
placeholder="请选择出生年月"
style="width: 100%">
</el-date-picker>
</template>
</el-table-column>
<el-table-column label="学位" min-width="120">
<template slot-scope="{row}">
<el-select v-model="row.grade" filterable placeholder="请选择学位" style="width: 100%">
<el-option
v-for="item in memberDegreeDict"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
</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="所在单位" min-width="160">
<template slot-scope="{row}">
<el-input v-model="row.deptName" disabled></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width="90" v-if="!isView">
<template slot-scope="scope">
<el-button size="mini" type="danger" @click="removeMember(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-form-item class="brand-form-actions" v-if="!isView">
<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>
<el-dialog title="选择工作室成员" :visible.sync="memberDialogVisible" width="760px" class="brand-member-dialog">
<div class="brand-member-search">
<el-input v-model="memberKeyword" placeholder="请输入姓名或工号" clearable @keyup.enter.native="loadMemberCandidates"></el-input>
<el-button type="primary" icon="el-icon-search" @click="loadMemberCandidates">查询</el-button>
</div>
<el-table :data="memberCandidates" border size="small" height="360" @selection-change="memberSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="loginname" label="工号" width="120"></el-table-column>
<el-table-column prop="username" label="姓名" width="120"></el-table-column>
<el-table-column prop="unitName" label="所属机构" show-overflow-tooltip></el-table-column>
</el-table>
<div slot="footer">
<el-button @click="memberDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmSelectMembers">确定</el-button>
</div>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
loading: false,
isView: false,
form: {
archive: 0,
status: 0,
createYear: String(new Date().getFullYear()),
leadIdentity: [],
brandStudioMembers: []
},
unionOptions: [],
leaderHonors: [],
leadPeopleDict: [],
brandOfficeTypeDict: [],
brandOfficeTrackDict: [],
memberDegreeDict: [],
leadPeopleEmplid: '',
memberDialogVisible: false,
memberKeyword: '',
memberCandidates: [],
selectedMemberCandidates: []
}
},
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
this.form.createYear = String(new Date().getFullYear())
},
async loadUnionOptions() {
const user = this.$store.state.user || {}
const union = user.union || {}
const roles = user.roles || []
const isAdmin = roles.some(item => item.code === 'SYSADMIN' || item.code === 'SCHOOL_UNION_ADMIN')
this.unionOptions = await this.$businessTool.listUnion(isAdmin ? null : union.id)
if (!this.form.unionId && this.unionOptions.length > 0) {
this.form.unionId = this.unionOptions[0].id
this.form.unionName = this.unionOptions[0].name
}
},
unionChange(unionId) {
const union = this.unionOptions.find(item => item.id === unionId)
this.form.unionName = union ? union.name : ''
},
loadDictOptions(code) {
return this.$axios.get('/open/common/dictOptions', {params: {code: code}}).then((res) => {
return this.getData(res) || []
})
},
loadDicts() {
Promise.all([
this.loadDictOptions('brand_office_lead_people'),
this.loadDictOptions('brand_office_type'),
this.loadDictOptions('brand_office_track'),
this.loadDictOptions('USER_ACADEMIC_DEGREE')
]).then(([leadPeopleDict, brandOfficeTypeDict, brandOfficeTrackDict, memberDegreeDict]) => {
this.leadPeopleDict = leadPeopleDict
this.brandOfficeTypeDict = brandOfficeTypeDict
this.brandOfficeTrackDict = brandOfficeTrackDict
this.memberDegreeDict = memberDegreeDict
})
},
loadLeaderHonors() {
this.$axios.get('/platform/zhgh/brand/application/leader').then((res) => {
const data = this.getData(res) || []
this.leaderHonors = data.map(item => {
item.label = item.userName + '' + item.emplid + ''
return item
})
if (this.form.leadPeople) {
const leader = this.leaderHonors.find(item => item.userName === this.form.leadPeople)
this.leadPeopleEmplid = leader ? leader.emplid : ''
}
})
},
leaderChange(emplid) {
const leader = this.leaderHonors.find(item => item.emplid === emplid)
if (leader) {
this.form.leadPeople = leader.userName
this.form.leadAge = leader.age
this.form.leadHonor = (leader.honorNames || []).join(',')
} else {
this.form.leadPeople = ''
this.form.leadAge = ''
this.form.leadHonor = ''
}
},
trackChange(track) {
this.form.domain = track
},
openMemberDialog() {
this.memberDialogVisible = true
this.loadMemberCandidates()
},
loadMemberCandidates() {
this.$axios.get('/platform/zhgh/brand/application/members', {
params: {
unionId: this.form.unionId,
keyword: this.memberKeyword
}
}).then((res) => {
this.memberCandidates = this.getData(res) || []
})
},
memberSelectionChange(selection) {
this.selectedMemberCandidates = selection
},
confirmSelectMembers() {
if (!this.selectedMemberCandidates.length) {
this.$message.warning('请至少选择一位工作室成员')
return
}
const existingIds = new Set((this.form.brandStudioMembers || []).map(item => item.userId || item.id))
const members = this.selectedMemberCandidates
.filter(item => !existingIds.has(item.id))
.map(item => ({
userId: item.id,
emplid: item.emplid || item.loginname,
name: item.name || item.username,
role: '',
birthdate: item.birthdate,
grade: item.grade,
professional: item.professional,
deptName: item.deptName || item.unitName
}))
this.form.brandStudioMembers = [...(this.form.brandStudioMembers || []), ...members]
this.form.memberNum = this.form.brandStudioMembers.length
this.memberDialogVisible = false
},
loadDetail(id) {
this.$axios.get('/platform/zhgh/brand/application/' + id).then((res) => {
const data = this.getData(res) || {}
data.brandStudioMembers = data.brandStudioMembers || []
data.memberNum = data.brandStudioMembers.length
data.leadIdentity = data.leadIdentity ? data.leadIdentity.split(',') : []
this.form = data
this.loadUnionOptions()
this.loadLeaderHonors()
})
},
addMember() {
this.form.brandStudioMembers.push({})
this.form.memberNum = this.form.brandStudioMembers.length
},
removeMember(index) {
this.form.brandStudioMembers.splice(index, 1)
this.form.memberNum = this.form.brandStudioMembers.length
},
save(status) {
this.loading = true
this.form.status = status
const params = Object.assign({}, this.form)
params.leadIdentity = (params.leadIdentity || []).join(',')
params.domain = params.track
params.memberNum = (params.brandStudioMembers || []).length
params.brandStudioMembersJson = JSON.stringify(params.brandStudioMembers || [])
delete params.brandStudioMembers
const request = this.form.id
? this.$axios.post('/platform/zhgh/brand/application/update', params)
: this.$axios.post('/platform/zhgh/brand/application', params)
request.then(() => {
this.$message.success('保存成功')
this.goBack()
}).finally(() => {
this.loading = false
})
},
goBack() {
window.location.href = '/platform/zhgh/brand/application/index'
}
},
created() {
this.loadDicts()
this.loadLeaderHonors()
const id = this.getParam('id')
this.isView = this.getParam('mode') === 'view'
if (id) {
this.loadDetail(id)
} else {
this.loadUserInfo()
this.loadUnionOptions()
}
}
})
</script>
<style>
.brand-member-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 0 12px;
padding: 0 2px 0 16px;
}
.brand-member-count {
color: #606266;
font-size: 13px;
}
.brand-member-table {
padding-left: 16px;
}
.brand-form-actions {
margin-top: 20px;
text-align: right;
}
.brand-member-search {
display: flex;
gap: 10px;
margin-bottom: 12px;
}
.brand-member-search .el-input {
flex: 1;
}
.brand-member-dialog .el-dialog__body {
padding-top: 12px;
padding-bottom: 12px;
}
</style>
<!--#
}
#-->
@@ -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,345 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<el-form :inline="true" :model="pageForm">
<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-input v-model="pageForm.name" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="pageData">查询</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="工作室列表"></table-tool>
<el-table :data="tableData" border style="width: 100%">
<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="150" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openDetail(row)">编辑品牌工作室</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<template #public>
<div>
<div class="process-title">编辑品牌工作室</div>
<el-tabs v-model="activeName">
<el-tab-pane name="base" label="基本信息">
<el-descriptions :column="2" border>
<el-descriptions-item label="工作室名称">{{form.name}}</el-descriptions-item>
<el-descriptions-item label="创立年度">{{form.createYear}}</el-descriptions-item>
<el-descriptions-item label="所属工会">{{form.unionName}}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{form.deptName}}</el-descriptions-item>
<el-descriptions-item label="领衔人">{{form.leadPeople}}</el-descriptions-item>
<el-descriptions-item label="领衔人年龄">{{form.leadAge}}</el-descriptions-item>
<el-descriptions-item label="领衔人身份">{{Array.isArray(form.leadIdentity) ? form.leadIdentity.join(',') : form.leadIdentity}}</el-descriptions-item>
<el-descriptions-item label="领衔人荣誉">{{form.leadHonor}}</el-descriptions-item>
<el-descriptions-item label="工作室类型">{{form.type}}</el-descriptions-item>
<el-descriptions-item label="工作室赛道">{{form.track}}</el-descriptions-item>
<el-descriptions-item label="工作室地址" :span="2">{{form.address}}</el-descriptions-item>
<el-descriptions-item label="工作室介绍" :span="2">{{form.introduction}}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{form.remark}}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<el-tab-pane name="members" label="成员信息">
<div class="brand-member-toolbar">
<span>成员人数:{{(form.brandStudioMembers || []).length}}</span>
<div>
<el-button type="primary" size="small" @click="openMemberDialog">选择成员</el-button>
<el-button type="primary" size="small" :loading="savingMembers" @click="saveMembers">保存人员</el-button>
</div>
</div>
<el-table :data="form.brandStudioMembers || []" border size="small">
<el-table-column label="姓名" prop="name" width="120"></el-table-column>
<el-table-column label="出生年月" prop="birthdate" width="180">
<template slot-scope="{row}">
<el-date-picker v-model="row.birthdate" type="month" value-format="yyyy-MM" style="width: 100%"></el-date-picker>
</template>
</el-table-column>
<el-table-column label="学位" prop="grade" min-width="180">
<template slot-scope="{row}">
<el-select v-model="row.grade" style="width: 100%">
<el-option v-for="item in memberDegreeDict" :key="item.value || item.code" :label="item.label || item.name" :value="item.value || item.code"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="职称" prop="professional" min-width="180">
<template slot-scope="{row}">
<el-input v-model="row.professional"></el-input>
</template>
</el-table-column>
<el-table-column label="所在单位" prop="deptName" min-width="180"></el-table-column>
<el-table-column label="操作" width="90">
<template slot-scope="scope">
<el-button size="mini" type="danger" @click="removeMember(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane name="honors" label="荣誉信息">
<div class="brand-member-toolbar">
<span>荣誉数量:{{(form.brandStudioHonors || []).length}}</span>
<div>
<el-button type="primary" size="small" @click="addHonor">添加荣誉</el-button>
<el-button type="primary" size="small" :loading="savingHonors" @click="saveHonors">保存荣誉</el-button>
</div>
</div>
<el-table :data="form.brandStudioHonors || []" border size="small">
<el-table-column label="荣誉称号" prop="honorName" min-width="160">
<template slot-scope="{row}">
<el-input v-model="row.honorName"></el-input>
</template>
</el-table-column>
<el-table-column label="获取年度" prop="requireYear" min-width="180">
<template slot-scope="{row}">
<el-date-picker v-model="row.requireYear" type="year" value-format="yyyy" style="width: 100%"></el-date-picker>
</template>
</el-table-column>
<el-table-column label="荣誉类别" prop="honorCategory" min-width="180">
<template slot-scope="{row}">
<el-select v-model="row.honorCategory" style="width: 100%">
<el-option label="外部荣誉" :value="1"></el-option>
<el-option label="行内荣誉" :value="2"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="操作" min-width="160">
<template slot-scope="scope">
<el-button size="mini" type="danger" @click="removeHonor(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<el-row type="flex" justify="end" class="brand-detail-actions">
</el-row>
</div>
</template>
</guava>
<el-dialog title="选择工作室成员" :visible.sync="memberDialogVisible" width="760px" class="brand-member-dialog">
<div class="brand-member-search">
<el-input v-model="memberKeyword" placeholder="请输入姓名或工号" clearable @keyup.enter.native="loadMemberCandidates"></el-input>
<el-button type="primary" icon="el-icon-search" @click="loadMemberCandidates">查询</el-button>
</div>
<el-table :data="memberCandidates" border size="small" height="360" @selection-change="memberSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="loginname" label="工号" width="120"></el-table-column>
<el-table-column prop="username" label="姓名" width="120"></el-table-column>
<el-table-column prop="unitName" label="所在单位" show-overflow-tooltip></el-table-column>
</el-table>
<div slot="footer">
<el-button @click="memberDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmSelectMembers">确定</el-button>
</div>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
pageForm: {
page: 1,
limit: 20,
status: 5,
createYear: String(new Date().getFullYear())
},
tableData: [],
activeName: 'base',
savingMembers: false,
savingHonors: false,
form: {
brandStudioMembers: [],
brandStudioHonors: [],
leadIdentity: []
},
memberDegreeDict: [],
memberDialogVisible: false,
memberKeyword: '',
memberCandidates: [],
selectedMemberCandidates: []
}
},
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 || []
})
},
loadDictOptions(code) {
return this.$axios.get('/open/common/dictOptions', {params: {code: code}}).then((res) => {
return this.getData(res) || []
})
},
loadDicts() {
Promise.all([
this.loadDictOptions('USER_ACADEMIC_DEGREE')
]).then(([memberDegreeDict]) => {
this.memberDegreeDict = memberDegreeDict
})
},
openDetail(row) {
this.$refs.guava.public(() => {
this.activeName = 'base'
this.$axios.get('/platform/zhgh/brand/application/' + row.id).then((res) => {
const data = this.getData(res) || {}
data.brandStudioMembers = data.brandStudioMembers || []
data.brandStudioHonors = data.brandStudioHonors || []
data.memberNum = data.brandStudioMembers.length
data.leadIdentity = data.leadIdentity ? data.leadIdentity.split(',') : []
this.form = data
})
})
},
openMemberDialog() {
this.memberDialogVisible = true
this.selectedMemberCandidates = []
this.loadMemberCandidates()
},
loadMemberCandidates() {
this.$axios.get('/platform/zhgh/brand/application/members', {
params: {
unionId: this.form.unionId,
keyword: this.memberKeyword
}
}).then((res) => {
this.memberCandidates = this.getData(res) || []
})
},
memberSelectionChange(selection) {
this.selectedMemberCandidates = selection
},
confirmSelectMembers() {
if (!this.selectedMemberCandidates.length) {
this.$message.warning('请至少选择一位工作室成员')
return
}
const existingIds = new Set((this.form.brandStudioMembers || []).map(item => item.userId || item.id))
const members = this.selectedMemberCandidates
.filter(item => !existingIds.has(item.id))
.map(item => ({
userId: item.id,
emplid: item.emplid || item.loginname,
name: item.name || item.username,
role: '',
birthdate: item.birthdate,
grade: item.grade,
professional: item.professional,
deptName: item.deptName || item.unitName
}))
this.form.brandStudioMembers = [...(this.form.brandStudioMembers || []), ...members]
this.form.memberNum = this.form.brandStudioMembers.length
this.memberDialogVisible = false
},
removeMember(index) {
this.form.brandStudioMembers.splice(index, 1)
this.form.memberNum = this.form.brandStudioMembers.length
},
addHonor() {
this.form.brandStudioHonors = this.form.brandStudioHonors || []
this.form.brandStudioHonors.push({
applicationId: this.form.id,
honorName: '',
requireYear: String(new Date().getFullYear()),
honorCategory: 1
})
},
removeHonor(index) {
this.form.brandStudioHonors.splice(index, 1)
},
buildSaveParams() {
const params = Object.assign({}, this.form)
params.status = 5
params.leadIdentity = (params.leadIdentity || []).join(',')
params.domain = params.track
params.memberNum = (params.brandStudioMembers || []).length
params.brandStudioMembersJson = JSON.stringify(params.brandStudioMembers || [])
delete params.brandStudioMembers
return params
},
saveMembers() {
this.savingMembers = true
const params = this.buildSaveParams()
this.$axios.post('/platform/zhgh/brand/application/honor', params).then(() => {
this.$message.success('保存人员成功')
this.pageData()
}).finally(() => {
this.savingMembers = false
})
},
saveHonors() {
this.savingHonors = true
const params = this.buildSaveParams()
params.brandStudioHonors = (this.form.brandStudioHonors || []).map(item => ({
applicationId: this.form.id,
honorName: item.honorName,
honorGrade: item.honorGrade,
requireYear: item.requireYear,
honorCategory: item.honorCategory
}))
params.brandStudioHonorsJson = JSON.stringify(params.brandStudioHonors)
this.$axios.post('/platform/zhgh/brand/application/honor', params).then(() => {
this.$message.success('保存荣誉成功')
this.pageData()
}).finally(() => {
this.savingHonors = false
})
}
},
created() {
this.loadDicts()
this.pageData()
}
})
</script>
<style>
.brand-detail-actions {
margin-top: 16px;
}
.brand-member-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.brand-member-search {
display: flex;
gap: 10px;
margin-bottom: 12px;
}
.brand-member-search .el-input {
flex: 1;
}
.brand-member-dialog .el-dialog__body {
padding-top: 12px;
padding-bottom: 12px;
}
</style>
<!--#
}
#-->
@@ -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,274 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="创立年度:">
<el-date-picker
placeholder="选择创立年度"
type="year"
style="width: 100%"
v-model="pageForm.createYear"
value-format="yyyy"
></el-date-picker>
</search-item>
<search-item label="工作室名称:">
<el-input
@keyup.enter.native="doSearch"
clearable
placeholder="请输入工作室名称"
style="width: 100%"
v-model="pageForm.name"
></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="申请列表">
<el-radio-group @change="doSearch" size="small" v-model="pageForm.audit">
<el-radio-button :label="true">已审核</el-radio-button>
<el-radio-button :label="false">未审核</el-radio-button>
</el-radio-group>
</table-tool>
<el-table :data="tableData" :size="tableSize">
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
<el-table-column label="工作室名称" prop="name" sortable show-overflow-tooltip min-width="180"></el-table-column>
<el-table-column label="创立年度" prop="createYear" sortable show-overflow-tooltip width="120"></el-table-column>
<el-table-column label="领衔人" prop="leadPeople" sortable show-overflow-tooltip width="120"></el-table-column>
<el-table-column label="所在单位" prop="deptName" sortable show-overflow-tooltip min-width="180"></el-table-column>
<el-table-column label="工作室类型" prop="type" sortable show-overflow-tooltip min-width="140"></el-table-column>
<el-table-column label="工作室赛道" prop="track" sortable show-overflow-tooltip min-width="140"></el-table-column>
<el-table-column label="成员人数" prop="memberNum" sortable show-overflow-tooltip width="110"></el-table-column>
<el-table-column label="工作室地址" prop="address" show-overflow-tooltip min-width="220"></el-table-column>
<el-table-column label="当前节点" prop="curTaskName" show-overflow-tooltip min-width="180"></el-table-column>
<el-table-column label="审核时间" prop="finishTime" show-overflow-tooltip min-width="180">
<template v-slot="{ row }">
<span>{{ row.finishTime ? $moment(row.finishTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="流程状态" prop="instanceState" show-overflow-tooltip min-width="180">
<template v-slot="{ row }">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template v-slot="{row}">
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskState === 10" @click="onAudit(row)" size="mini" type="primary">审核</el-button>
<el-button v-if="row.canRevoke" @click="onRevoke(row)" size="mini" type="danger">撤回</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #public>
<div>
<el-tabs tab-position="top" v-model="activeName">
<el-tab-pane name="1" label="基础信息">
<div class="process-title">
品牌工作室申请
<el-link type="primary" @click="openChart">点击查看流程图</el-link>
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="工作室名称">{{detail.name}}</el-descriptions-item>
<el-descriptions-item label="创立年度">{{detail.createYear}}</el-descriptions-item>
<el-descriptions-item label="所属工会">{{detail.unionName}}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{detail.deptName}}</el-descriptions-item>
<el-descriptions-item label="领衔人">{{detail.leadPeople}}</el-descriptions-item>
<el-descriptions-item label="领衔人年龄">{{detail.leadAge}}</el-descriptions-item>
<el-descriptions-item label="领衔人身份">{{detail.leadIdentity}}</el-descriptions-item>
<el-descriptions-item label="领衔人荣誉">{{detail.leadHonor}}</el-descriptions-item>
<el-descriptions-item label="工作室类型">{{detail.type}}</el-descriptions-item>
<el-descriptions-item label="所属赛道">{{detail.track}}</el-descriptions-item>
<el-descriptions-item label="工作室地址" :span="2">{{detail.address}}</el-descriptions-item>
<el-descriptions-item label="工作室介绍" :span="2">{{detail.introduction}}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{detail.remark}}</el-descriptions-item>
</el-descriptions>
<snaker-chart ref="snakerChartRef"></snaker-chart>
<div v-if="showApprovalForm">
<div class="process-title">
{{formData.taskName}}
</div>
<el-form :model="formData" ref="formRef" label-width="0" label-suffix="" class="flow-task-form">
<el-form-item label="审批意见" prop="remark"
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<user-opinion-textarea v-model="formData.remark"></user-opinion-textarea>
</el-form-item>
</el-form>
<el-row type="flex" justify="end">
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
</el-row>
</div>
</el-tab-pane>
<el-tab-pane name="2" label="成员信息">
<div class="process-title">成员信息</div>
<el-table :data="detail.brandStudioMembers || []" border size="small">
<el-table-column label="姓名" prop="name" width="120"></el-table-column>
<el-table-column label="出生年月" prop="birthdate" width="120"></el-table-column>
<el-table-column label="学位" prop="grade" show-overflow-tooltip></el-table-column>
<el-table-column label="职称" prop="professional" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="deptName" show-overflow-tooltip></el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane name="3" label="审核记录">
<template v-if="doneTasks.length > 0" v-for="task in doneTasks">
<div class="mt10">
<div class="process-title">{{ task.displayName }}</div>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id"
v-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">{{ task.ext.initiatorName }}({{task.ext.initiatorAccount}})</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" v-if="!task.ext.isFirstTaskNode">{{ task.taskFormData.opinion || task.taskFormData.remark || task.taskFormData.approvalComment }}</el-descriptions-item>
</el-descriptions>
</div>
</template>
<template v-else>
<el-empty description="暂无审核记录"></el-empty>
</template>
</el-tab-pane>
</el-tabs>
</div>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
pageForm: {
audit: false,
createYear: String(new Date().getFullYear())
},
detail: {},
formData: {},
currentRow: {},
showApprovalForm: false,
activeName: "1",
doneTasks: []
}
},
methods: {
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
onView(row) {
this.$refs.guava.public(() => {
this.currentRow = row
this.activeName = "1"
this.showApprovalForm = false
this.loadDetail(row.id)
this.getDoneTasks(row)
})
},
onAudit(row) {
this.$refs.guava.public(() => {
this.currentRow = row
this.activeName = "1"
this.formData = {
applicationId: row.id,
processInstanceId: row.processInstanceId,
processInstanceTaskId: row.processInstanceTaskId || row.taskId,
taskName: row.curTaskName,
remark: ''
}
this.showApprovalForm = true
this.loadDetail(row.id)
this.getDoneTasks(row)
})
},
loadDetail(id) {
this.$axios.get('/platform/zhgh/brand/application/' + id).then((res) => {
this.detail = this.getData(res) || {}
this.detail.brandStudioMembers = this.detail.brandStudioMembers || []
})
},
getDoneTasks(row) {
this.$axios.post('/flow/common/doneTasks', {
instanceId: row.processInstanceId || row.instanceId,
bizId: row.id
}).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
openChart() {
if (!this.currentRow.processInstanceId || !this.currentRow.instanceProcessDefineId) {
this.$message.warning('暂无流程图信息')
return
}
this.$refs.snakerChartRef.onOpenFull(this.currentRow.instanceProcessDefineId, this.currentRow.processInstanceId)
},
handleTaskAction(val) {
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post('/platform/zhgh/brand/flow/audit', {
...this.formData,
submitType: val
}).then((res) => {
if (res.code === 0) {
this.$refs.guava.index()
this.$message.success(res.msg)
this.doSearch()
}
})
})
},
onRevoke(row) {
this.$confirm("您确定要撤回吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.doSearch()
}
})
})
},
async pageData() {
const resp = await this.$axios.post('/platform/zhgh/brand/application/auditPage', this.pageForm)
if (resp.code === 0) {
this.tableData = resp.data.list
this.pageForm.totalCount = resp.data.totalCount
} else {
this.$message.warning(resp.msg)
}
}
},
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,138 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<el-form :inline="true" :model="pageForm">
<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-input v-model="pageForm.name" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="pageData">查询</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="工作室台账"></table-tool>
<el-table :data="tableData" border style="width: 100%">
<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="honorName" label="荣誉称号" min-width="150"></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="180"></el-table-column>
<el-table-column label="操作" width="90" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<template #public>
<div>
<div class="process-title">查看品牌工作室</div>
<el-tabs v-model="activeName">
<el-tab-pane name="base" label="基本信息">
<el-descriptions :column="2" border>
<el-descriptions-item label="工作室名称">{{form.name}}</el-descriptions-item>
<el-descriptions-item label="创立年度">{{form.createYear}}</el-descriptions-item>
<el-descriptions-item label="所属工会">{{form.unionName}}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{form.deptName}}</el-descriptions-item>
<el-descriptions-item label="领衔人">{{form.leadPeople}}</el-descriptions-item>
<el-descriptions-item label="领衔人年龄">{{form.leadAge}}</el-descriptions-item>
<el-descriptions-item label="领衔人身份">{{form.leadIdentity}}</el-descriptions-item>
<el-descriptions-item label="领衔人荣誉">{{form.leadHonor}}</el-descriptions-item>
<el-descriptions-item label="工作室类型">{{form.type}}</el-descriptions-item>
<el-descriptions-item label="所属领域">{{form.track}}</el-descriptions-item>
<el-descriptions-item label="工作室地址" :span="2">{{form.address}}</el-descriptions-item>
<el-descriptions-item label="工作室介绍" :span="2">{{form.introduction}}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{form.remark}}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<el-tab-pane name="members" label="成员信息">
<el-table :data="form.brandStudioMembers || []" border size="small">
<el-table-column label="姓名" prop="name" width="120"></el-table-column>
<el-table-column label="出生年月" prop="birthdate" width="120"></el-table-column>
<el-table-column label="学位" prop="grade" show-overflow-tooltip></el-table-column>
<el-table-column label="职称" prop="professional" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="deptName" show-overflow-tooltip></el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane name="honors" label="荣誉信息">
<el-table :data="form.brandStudioHonors || []" border size="small">
<el-table-column label="荣誉称号" prop="honorName" min-width="160"></el-table-column>
<el-table-column label="获取年度" prop="requireYear" min-width="120"></el-table-column>
<el-table-column label="荣誉类别" prop="honorCategory" min-width="120">
<template slot-scope="{row}">
<span>{{row.honorCategory === 1 ? '外部荣誉' : (row.honorCategory === 2 ? '行内荣誉' : row.honorCategory)}}</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
pageForm: {
page: 1,
limit: 20,
status: 5,
createYear: String(new Date().getFullYear())
},
tableData: [],
activeName: 'base',
form: {
brandStudioMembers: [],
brandStudioHonors: []
}
}
},
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 || []
})
},
openView(row) {
this.$refs.guava.public(() => {
this.activeName = 'base'
this.$axios.get('/platform/zhgh/brand/application/' + row.id).then((res) => {
const data = this.getData(res) || {}
data.brandStudioMembers = data.brandStudioMembers || []
data.brandStudioHonors = data.brandStudioHonors || []
this.form = 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>